应对软件核心的复杂性
Tackling Complexity in the Heart of Software
新泽西州上萨德尔河 • 波士顿 • 印第安纳波利斯 • 旧金山
• 纽约 • 多伦多 • 蒙特利尔 • 伦敦 • 慕尼黑 • 巴黎 • 马德里 •
开普敦 • 悉尼 • 东京 • 新加坡 • 墨西哥城
Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
许多制造商和销售商用于区分其产品的名称都已注册为商标。本书中若出现此类名称,且艾迪生-韦斯利出版社知晓其已注册为商标,则这些名称的首字母或全部字母均以大写形式印刷。
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.
作者和出版商已尽力确保本书内容的准确性,但不作任何明示或暗示的保证,亦不对任何错误或遗漏承担责任。对于因使用本书所含信息或程序而导致的任何附带或间接损失,概不承担任何责任。
The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.
图片来源请参见第517页。
See page 517 for photo credits.
出版社为批量订购本书提供折扣,适用于大宗采购和特价促销活动。欲了解更多信息,请联系:
The publisher offers discounts on this book when ordered in quantity for bulk purchases and special sales. For more information, please contact:
美国企业及政府销售部
(800) 382-3419
corpsales@pearsontechgroup.com
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
美国以外地区的销售请联系:
For sales outside of the U.S., please contact:
国际销售
international@pearsoned.com
International Sales
international@pearsoned.com
访问 Addison-Wesley 网站:www.awprofessional.com
Visit Addison-Wesley on the Web: www.awprofessional.com
美国国会图书馆出版物数据
:埃文斯,埃里克,1962–
领域驱动设计:解决软件核心的复杂性 / 埃里克·
埃文斯。
页数:厘米。
包含参考文献和索引。ISBN
0-321-12521-5
1. 计算机软件—开发。2. 面向对象编程
(计算机科学) I. 书名。QA76.76.D47E82
2003
005.1—dc21
2003050331
Library of Congress Cataloging-in-Publication Data
Evans, Eric, 1962–
Domain-driven design : tackling complexity in the heart of software / Eric
Evans.
p. cm.
Includes bibliographical references and index.
ISBN 0-321-12521-5
1. Computer software—Development. 2. Object-oriented programming
(Computer science) I. Title.
QA76.76.D47E82 2003
005.1—dc21
2003050331
版权所有 © 2004 Eric Evans
Copyright © 2004 by Eric Evans
版权所有。未经出版商事先许可,不得以任何形式或任何方式(包括电子、机械、影印、录音或其他方式)复制、存储于检索系统或传播本出版物的任何部分。美国印刷。加拿大同步出版。
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. Published simultaneously in Canada.
如需获得使用本作品素材的许可,请提交书面申请至:
For information on obtaining permission for use of material from this work, please submit a written request to:
培生教育集团
版权与合同部,地址:马萨诸塞州
波士顿博伊尔斯顿街500号900室,邮编:02116 ,传真:(617) 671-3447
Pearson Education, Inc.
Rights and Contracts Department
500 Boylston Street, Suite 900
Boston, MA 02116
Fax: (617) 671-3447
ISBN 0-321-12521-5
本书由位于马萨诸塞州韦斯特福德的Courier印刷厂使用再生纸在美国印刷
。
第二十一版,2015年7月。
ISBN 0-321-12521-5
Text printed in the United States on recycled paper at Courier in Westford,
Massachusetts.
Twenty-First printing, July 2015
“每一位有思想的软件开发人员的书架上都应该有这本书。”
“This book belongs on the shelf of every thoughtful software developer.”
——肯特·贝克
—Kent Beck
“Eric Evans 写了一本很棒的书,讲述了如何让你的软件设计与你对所解决问题领域的心智模型相匹配。
“Eric Evans has written a fantastic book on how you can make the design of your software match your mental model of the problem domain you are addressing.
“他的书与极限编程(XP)非常契合。它并非着眼于描绘领域图景,而是关注你如何思考领域,如何用语言描述领域,以及如何组织软件以反映你对领域的理解不断加深。埃里克认为,对问题领域的理解在项目后期和初期同样重要,因此重构是他方法论的重要组成部分。”
“His book is very compatible with XP. It is not about drawing pictures of a domain; it is about how you think of it, the language you use to talk about it, and how you organize your software to reflect your improving understanding of it. Eric thinks that learning about your problem domain is as likely to happen at the end of your project as at the beginning, and so refactoring is a big part of his technique.
“这本书读起来很有趣。埃里克讲了很多精彩的故事,而且他的文笔也很好。我认为这本书是软件开发人员的必读之作——它将会成为一部经典之作。”
“The book is a fun read. Eric has lots of interesting stories, and he has a way with words. I see this book as essential reading for software developers—it is a future classic.”
——拉尔夫·约翰逊, 《设计模式》作者
—Ralph Johnson, author of Design Patterns
“如果你觉得你在面向对象编程方面的投入没有得到回报,这本书会告诉你你忘记做了什么。”
“If you don’t think you are getting value from your investment in object-oriented programming, this book will tell you what you’ve forgotten to do.”
——沃德·坎宁安
—Ward Cunningham
“埃里克成功地捕捉到了经验丰富的物体设计师一直以来都在使用的设计流程的一部分,但我们作为一个群体,却始终未能成功地将其传达给业内其他人士。我们曾零星地分享过这些知识……但我们从未系统地整理和系统化构建领域逻辑的原则。这本书意义重大。”
“What Eric has managed to capture is a part of the design process that experienced object designers have always used, but that we have been singularly unsuccessful as a group in conveying to the rest of the industry. We’ve given away bits and pieces of this knowledge . . . but we’ve never organized and systematized the principles of building domain logic. This book is important.”
——Kyle Brown, 《使用 IBM WebSphere 进行企业级 Java 编程》一书的作者
—Kyle Brown, author of Enterprise Java Programming with IBM WebSphere
“埃里克·埃文斯令人信服地论证了领域建模作为开发核心的重要性,并提供了一套实现领域建模的可靠框架和技术。这是永恒的智慧,即使当下流行的各种方法论过时之后,它仍然适用。”
“Eric Evans convincingly argues for the importance of domain modeling as the central focus of development and provides a solid framework and set of techniques for accomplishing it. This is timeless wisdom, and will hold up long after the methodologies du jour have gone out of fashion.”
——戴夫·柯林斯, 《面向对象用户界面设计》一书的作者
—Dave Collins, author of Designing Object-Oriented User Interfaces
“埃里克将实际经验建模和构建业务应用程序融入到一本实用性很强的书中。本书以一位值得信赖的实践者的视角撰写,埃里克对通用语言、与用户共享模型的好处、对象生命周期管理、逻辑和物理应用程序结构以及深度重构的过程和结果的描述,都是对我们这个领域的重要贡献。”
“Eric weaves real-world experience modeling—and building—business applications into a practical, useful book. Written from the perspective of a trusted practitioner, Eric’s descriptions of ubiquitous language, the benefits of sharing models with users, object life-cycle management, logical and physical application structuring, and the process and results of deep refactoring are major contributions to our field.”
——卢克·霍曼, 《超越软件架构》的作者
—Luke Hohmann, author of Beyond Software Architecture
Part I Putting the Domain Model to Work
Chapter 1: Crunching Knowledge
Ingredients of Effective Modeling
Chapter 2: Communication and the Use of Language
Chapter 3: Binding Model and Implementation
Modeling Paradigms and Tool Support
Letting the Bones Show: Why Models Matter to Users
Part II The Building Blocks of a Model-Driven Design
Chapter 4: Isolating the Domain
The Domain Layer Is Where the Model Lives
Chapter 5: A Model Expressed in Software
ENTITIES (A.K.A. REFERENCE OBJECTS)
Designing the Identity Operation
Designing Associations That Involve VALUE OBJECTS
SERVICES and the Isolated Domain Layer
The Pitfalls of Infrastructure-Driven Packaging
Why the Object Paradigm Predominates
Sticking with MODEL-DRIVEN DESIGN When Mixing Paradigms
Chapter 6: The Life Cycle of a Domain Object
Choosing FACTORIES and Their Sites
When a Constructor Is All You Need
Where Does Invariant Logic Go?
ENTITY FACTORIES Versus VALUE OBJECT FACTORIES
Client Code Ignores REPOSITORY Implementation; Developers Do Not
Working Within Your Frameworks
The Relationship with FACTORIES
Designing Objects for Relational Databases
Chapter 7: Using the Language: An Extended Example
Introducing the Cargo Shipping System
Isolating the Domain: Introducing the Applications
Distinguishing ENTITIES and VALUE OBJECTS
Designing Associations in the Shipping Domain
Sample Application Feature: Changing the Destination of a Cargo
Sample Application Feature: Repeat Business
FACTORIES and Constructors for Cargo
Pause for Refactoring: An Alternative Design of the Cargo AGGREGATE
Introducing a New Feature: Allocation Checking
Enhancing the Model: Segmenting the Business
Part III Refactoring Toward Deeper Insight
Epilogue: A Cascade of New Insights
Chapter 9: Making Implicit Concepts Explicit
How to Model Less Obvious Kinds of Concepts
Applying and Implementing SPECIFICATION
INTENTION-REVEALING INTERFACES
Extending SPECIFICATIONS in a Declarative Style
Draw on Established Formalisms, When You Can
Chapter 11: Applying Analysis Patterns
Chapter 12: Relating Design Patterns to the Model
Chapter 13: Refactoring Toward Deeper Insight
Chapter 14: Maintaining Model Integrity
Recognizing Splinters Within a BOUNDED CONTEXT
Testing at the CONTEXT Boundaries
Organizing and Documenting CONTEXT MAPS
Relationships Between BOUNDED CONTEXTS
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS
Designing the Interface of the ANTICORRUPTION LAYER
Implementing the ANTICORRUPTION LAYER
Choosing Your Model Context Strategy
Accepting That Which We Cannot Change: Delineating the External Systems
Relationships with the External Systems
Catering to Special Needs with Distinct Models
When Your Project Is Already Under Way
Merging CONTEXTS: SEPARATE WAYS → SHARED KERNEL
Merging CONTEXTS: SHARED KERNEL → CONTINUOUS INTEGRATION
OPEN HOST SERVICE → PUBLISHED LANGUAGE
An Escalation of Distillations
The Distillation Document as Process Tool
GENERIC SUBDOMAIN Versus COHESIVE MECHANISM
When a MECHANISM Is Part of the CORE DOMAIN
Distilling to a Declarative Style
The Costs of Creating a SEGREGATED CORE
Chapter 16: Large-Scale Structure
The “Naive Metaphor” and Why We Don’t Need It
How Restrictive Should a Structure Be?
Refactoring Toward a Fitting Structure
Communication and Self-Discipline
Restructuring Yields Supple Design
Distillation Lightens the Load
Chapter 17: Bringing the Strategy Together
Combining Large-Scale Structures and BOUNDED CONTEXTS
Combining Large-Scale Structures and Distillation
Emergent Structure from Application Development
A Customer-Focused Architecture Team
Six Essentials for Strategic Design Decision Making
The Same Goes for the Technical Frameworks
软件开发之所以复杂,原因有很多。但这种复杂性的核心在于问题领域本身的复杂性。如果你试图将自动化引入复杂的人类活动,那么你的软件无法回避这种复杂性——它所能做的只是控制它。
There are many things that make software development complex. But the heart of this complexity is the essential intricacy of the problem domain itself. If you’re trying to add automation to complicated human enterprise, then your software cannot dodge this complexity—all it can do is control it.
控制复杂性的关键在于构建一个优秀的领域模型。这种模型超越了对领域的表面描述,引入了底层结构,从而为软件开发人员提供了所需的支持。优秀的领域模型价值连城,但构建起来却并非易事。能够做好领域模型的人寥寥无几,而且也很难教授。
The key to controlling complexity is a good domain model, a model that goes beyond a surface vision of a domain by introducing an underlying structure, which gives the software developers the leverage they need. A good domain model can be incredibly valuable, but it’s not something that’s easy to make. Few people can do it well, and it’s very hard to teach.
埃里克·埃文斯是少数几个能出色构建领域模型的人之一。我与他合作时发现了这一点——能遇到比自己更优秀的客户,真是太棒了。我们的合作虽然短暂,却乐趣无穷。此后我们一直保持联系,我也见证了这本书的慢慢成型。
Eric Evans is one of those few who can create domain models well. I discovered this by working with him—one of those wonderful times when you find a client who’s more skilled than you are. Our collaboration was short but enormous fun. Since then we’ve stayed in touch, and I’ve watched this book gestate slowly.
等待是值得的。
It’s been well worth the wait.
这本书逐渐演变成一部满足了其宏大抱负的作品:描述并构建一套关于领域建模艺术的词汇体系。它旨在提供一个参考框架,使我们能够解释这项活动,并教授这项难以掌握的技能。在本书的创作过程中,它给了我许多新的想法,我相信即使是经验丰富的概念建模专家,也能从本书中获得大量新的启发。
This book has evolved into one that satisfies a huge ambition: To describe and build a vocabulary about the very art of domain modeling. To provide a frame of reference through which we can explain this activity as well as teach this hard-to-learn skill. It’s a book that’s given me many new ideas as it has taken shape, and I’d be astonished if even old hands at conceptual modeling don’t get a raft of new ideas from reading this book.
Eric 也巩固了我们多年来学到的许多东西。首先,在领域建模中,你不应该将概念与实现割裂开来。一个高效的领域建模师不仅能与会计师一起在白板上进行建模,还能与程序员一起编写 Java 代码。这部分原因在于,你不能构建一个……仅凭概念模型而不考虑实现问题是不够的。但概念与实现密不可分的主要原因在于:领域模型的最大价值在于它提供了一种通用语言,将领域专家和技术人员联系起来。
Eric also cements many of the things that we’ve learned over the years. First, in domain modeling, you shouldn’t separate the concepts from the implementation. An effective domain modeler can not only use a whiteboard with an accountant, but also write Java with a programmer. Partly this is true because you cannot build a useful conceptual model without considering implementation issues. But the primary reason why concepts and implementation belong together is this: The greatest value of a domain model is that it provides a ubiquitous language that ties domain experts and technologists together.
从这本书中你还会学到一点:领域模型并非先建模后实现。和许多人一样,我已经不再认同“先设计后构建”的分阶段思维。但埃里克的经验告诉我们,真正强大的领域模型会随着时间的推移而不断演进,即使是最有经验的建模人员也会发现,他们在系统最初发布之后才迸发出最佳创意。
Another lesson you’ll learn from this book is that domain models aren’t first modeled and then implemented. Like many people, I’ve come to reject the phased thinking of “design, then build.” But the lesson of Eric’s experience is that the really powerful domain models evolve over time, and even the most experienced modelers find that they gain their best ideas after the initial releases of a system.
我认为,也希望,这本书会成为一本极具影响力的著作。它不仅能为这个纷繁复杂的领域构建结构和逻辑,还能教会很多人如何使用这一宝贵的工具。领域模型无论使用何种语言或环境实现,都能对软件开发进行有效管控,产生深远的影响。
I think, and hope, that this will be an enormously influential book. One that will add structure and cohesion to a very slippery field while it teaches a lot of people how to use a valuable tool. Domain models can have big consequences in controlling software development—in whatever language or environment they are implemented.
最后还有一点很重要。我最欣赏这本书的一点是,埃里克并不避讳谈论自己失败的经历。大多数作者都喜欢摆出一副高高在上、不偏不倚的姿态。但埃里克坦诚地告诉我们,和我们大多数人一样,他也尝过成功和失败的滋味。重要的是,他能从成功和失败中吸取教训——而对我们来说,更重要的是,他能将这些经验传授给我们。
One final yet important thought. One of things I most respect about this book is that Eric is not afraid to talk about the times when he hasn’t been successful. Most authors like to maintain an air of disinterested omnipotence. Eric makes it clear that like most of us, he’s tasted both success and disappointment. The important thing is that he can learn from both—and more important for us is that he can pass on his lessons.
马丁·福勒
2003年4月
Martin Fowler
April 2003
至少在过去的20年里,领先的软件设计师们一直将领域建模和设计视为至关重要的课题,然而令人惊讶的是,关于具体需要做什么以及如何去做,却鲜有著述。尽管从未被清晰地阐述过,但一种理念已在面向对象设计领域悄然兴起,我称之为领域驱动设计。
Leading software designers have recognized domain modeling and design as critical topics for at least 20 years, yet surprisingly little has been written about what needs to be done or how to do it. Although it has never been formulated clearly, a philosophy has emerged as an undercurrent in the object community, a philosophy I call domain-driven design.
过去十年,我一直在多个商业和技术领域开发复杂系统。在工作中,我尝试运用面向对象开发领域领先者提出的最佳实践进行设计和开发。我的一些项目非常成功,也有一些失败了。成功项目的共同特点是都拥有一个丰富的领域模型,该模型在设计迭代过程中不断完善,最终成为项目不可或缺的一部分。
I have spent the past decade developing complex systems in several business and technical domains. In my work, I have tried best practices in design and development process as they have emerged from the leaders in object-oriented development. Some of my projects were very successful; a few failed. A feature common to the successes was a rich domain model that evolved through iterations of design and became part of the fabric of the project.
本书提供了一个用于制定设计决策的框架和一套用于讨论领域设计的专业术语。它融合了广泛认可的最佳实践以及我自身的见解和经验。面对复杂领域的软件开发团队可以利用这个框架系统地进行领域驱动设计。
This book provides a framework for making design decisions and a technical vocabulary for discussing domain design. It is a synthesis of widely accepted best practices along with my own insights and experiences. Software development teams facing complex domains can use this framework to approach domain-driven design systematically.
有三个项目给我留下了深刻的印象,它们生动地展现了领域设计实践如何显著影响开发成果。虽然这三个项目都交付了实用的软件,但只有一个项目实现了其宏伟目标,并开发出了能够持续演进以满足组织不断变化的需求的复杂软件。
Three projects stand out in my memory as vivid examples of how dramatically domain design practice can affect development results. Although all three projects delivered useful software, only one achieved its ambitious objectives and produced complex software that continued to evolve to meet the ongoing needs of the organization.
我亲眼目睹了一个项目迅速启动,交付了一个实用、简单的基于Web的交易系统。开发人员的工作进展神速。他们凭感觉开发,但这并没有阻碍他们,因为简单的软件无需过多设计就能编写。由于最初的成功,人们对未来的开发寄予厚望。正是在那时,我被邀请参与第二个版本的开发。仔细观察后,我发现他们缺乏领域模型,甚至连项目通用语言都没有,而且设计结构混乱。项目负责人不同意我的评估,我拒绝了这份工作。一年后,团队陷入困境,无法交付第二个版本。虽然他们的技术运用并不出色,但真正让他们束手无策的是业务逻辑。他们的第一个版本过早地僵化成了一个维护成本极高的遗留系统。
I watched one project get out of the gate fast, by delivering a useful, simple Web-based trading system. Developers were flying by the seat of their pants, but this didn’t hinder them because simple software can be written with little attention to design. As a result of this initial success, expectations for future development were sky-high. That is when I was asked to work on the second version. When I took a close look, I saw that they lacked a domain model, or even a common language on the project, and were saddled with an unstructured design. The project leaders did not agree with my assessment, and I declined the job. A year later, the team found itself bogged down and unable to deliver a second version. Although their use of technology was not exemplary, it was the business logic that over-came them. Their first release had ossified prematurely into a high-maintenance legacy.
要突破复杂性的瓶颈,就需要更认真地对待领域逻辑的设计。我职业生涯早期很幸运地参与了一个重视领域设计的项目。这个项目所在的领域至少与第一个项目一样复杂,最初也取得了一定的成功,交付了一个简单的机构交易员应用程序。但与第一个项目不同的是,最初的交付之后,开发工作不断加速推进。每一次迭代都为集成和完善前一版本的功能开辟了令人兴奋的新途径。团队能够灵活地响应交易员的需求,并不断扩展其能力。这种上升势头直接归功于一个精辟的领域模型,该模型经过反复完善并以代码形式表达出来。随着团队对领域理解的加深,模型也随之深化。不仅开发人员之间的沟通质量得到了提高,开发人员与领域专家之间的沟通质量也得到了提升,而且设计不仅没有增加维护负担,反而变得更加易于修改和扩展。
Lifting this ceiling on complexity calls for a more serious approach to the design of domain logic. Early in my career, I was fortunate to end up on a project that did emphasize domain design. This project, in a domain at least as complex as the first one, also started with a modest initial success, delivering a simple application for institutional traders. But in this case, the initial delivery was followed up with successive accelerations of development. Each iteration opened exciting new options for integrating and elaborating the functionality of the previous release. The team was able to respond to the needs of the traders with flexibility and expanding capability. This upward trajectory was directly attributable to an incisive domain model, repeatedly refined and expressed in code. As the team gained new insight into the domain, the model deepened. The quality of communication improved not only among developers but also between developers and domain experts, and the design—far from imposing an ever-heavier maintenance burden—became easier to modify and extend.
遗憾的是,项目并非仅仅通过认真对待模型就能进入良性循环。我过去参与的一个项目最初雄心勃勃,计划基于领域模型构建一个全球企业系统,但经过多年的挫败后,它降低了目标,回归了传统模式。团队拥有优秀的工具和对业务的深刻理解,并且对建模非常重视。然而,开发人员角色划分不当,导致建模与实现脱节,最终的设计未能反映出深入的分析。总之,详细的业务对象设计不够严谨,无法支持它们之间的整合。在复杂的应用中,由于开发人员技能水平参差不齐,他们对创建基于模型的对象(这些对象同时也是实用的运行软件)的非正式风格和技术体系缺乏了解,因此反复迭代并未改进代码。随着时间的推移,开发工作陷入了复杂性的泥潭,团队也失去了对系统的统一愿景。经过数年的努力,该项目最终确实开发出了功能尚可的软件,但团队也放弃了最初的雄心壮志以及对模型的关注。
Unfortunately, projects don’t arrive at such a virtuous cycle just by taking models seriously. One project from my past started with lofty aspirations to build a global enterprise system based on a domain model, but after years of disappointment, it lowered its sights and settled into conventionality. The team had good tools and a good understanding of the business, and it gave careful attention to modeling. But a poorly chosen separation of developer roles disconnected modeling from implementation, so that the design did not reflect the deep analysis that was going on. In any case, the design of detailed business objects was not rigorous enough to support combining them in elaborate applications. Repeated iteration produced no improvement in the code, due to uneven skill levels among developers, who had no awareness of the informal body of style and technique for creating model-based objects that also function as practical, running software. As months rolled by, development work became mired in complexity and the team lost its cohesive vision of the system. After years of effort, the project did produce modest, useful software, but the team had given up its early ambitions along with the model focus.
很多因素都可能导致项目偏离轨道:官僚主义、目标不明确、资源匮乏等等。但设计方法在很大程度上决定了软件的复杂程度。当复杂性失控时,开发人员将无法充分理解软件,也就无法轻松安全地对其进行修改或扩展。另一方面,好的设计能够创造机会,让我们充分利用这些复杂的功能。
Many things can put a project off course: bureaucracy, unclear objectives, and lack of resources, to name a few. But it is the approach to design that largely determines how complex software can become. When complexity gets out of hand, developers can no longer understand the software well enough to change or extend it easily and safely. On the other hand, a good design can create opportunities to exploit those complex features.
有些设计因素是技术性的。网络、数据库以及软件的其他技术层面都投入了大量精力进行设计。许多书籍都探讨了如何解决这些问题。无数开发人员不断磨练技能,紧跟每一项技术进步。
Some design factors are technological. A great deal of effort has gone into the design of networks, databases, and other technical dimensions of software. Many books have been written about how to solve these problems. Legions of developers have cultivated their skills and followed each technical advancement.
然而,许多应用程序最主要的复杂性并非来自技术层面,而是来自用户领域本身,即用户的活动或业务。如果设计中没有妥善处理这种领域复杂性,那么即使基础设施技术构思精良也无济于事。成功的设计必须系统地解决软件的这一核心问题。
Yet the most significant complexity of many applications is not technical. It is in the domain itself, the activity or business of the user. When this domain complexity is not handled in the design, it won’t matter that the infrastructural technology is well conceived. A successful design must systematically deal with this central aspect of the software.
本书的前提有两点:
The premise of this book is twofold:
1.对于大多数软件项目而言,主要关注点应该是领域和领域逻辑。
1. For most software projects, the primary focus should be on the domain and domain logic.
2.复杂的领域设计应该基于模型。
2. Complex domain designs should be based on a model.
领域驱动设计既是一种思维方式,也是一套优先级排序方法,旨在加速那些必须处理以下问题的软件项目:复杂的领域。为了实现这一目标,本书提出了一系列全面的设计实践、技术和原则。
Domain-driven design is both a way of thinking and a set of priorities, aimed at accelerating software projects that have to deal with complicated domains. To accomplish that goal, this book presents an extensive set of design practices, techniques, and principles.
设计类书籍,流程类书籍,它们之间很少相互提及。每个主题本身都很复杂。这是一本设计类书籍,但我认为设计与流程密不可分。设计理念必须成功实施,否则就会沦为枯燥的学术讨论。
Design books. Process books. They seldom even reference each other. Each topic is complex in its own right. This is a design book, but I believe that design and process are inextricable. Design concepts must be implemented successfully or else they will dry up into academic discussion.
人们学习设计技巧时,往往会被各种可能性所吸引。然而,实际项目的种种难题却接踵而至。他们发现,新的设计理念无法与必须使用的技术完美契合。或者,他们不知道何时应该为了节省时间而放弃某个设计方案,何时又应该坚持到底,找到一个更简洁的解决方案。开发人员之间可以也确实会抽象地讨论设计原则的应用,但更自然的交流方式是探讨实际操作中如何实现这些原则。因此,尽管这是一本设计书籍,但我会在必要时打破人为的界限,深入探讨流程。这将有助于我们更好地理解设计原则的实际应用。
When people learn design techniques, they feel excited by the possibilities. Then the messy realities of a real project descend on them. They can’t fit the new design ideas with the technology they must use. Or they don’t know when to let go of a particular design aspect in the interest of time and when to dig in their heels and find a clean solution. Developers can and do talk with each other abstractly about the application of design principles, but it is more natural to talk about how real things get done. So, although this is a design book, I’m going to barge right across that artificial boundary into process when I need to. This will help put design principles in context.
本书并非局限于某种特定的方法论,而是面向新兴的“敏捷开发流程”体系。具体而言,本书假定项目中已实施了两项实践。这两项实践是应用本书方法的前提条件。
This book is not tied to a particular methodology, but it is oriented toward the new family of “Agile development processes.” Specifically, it assumes that a couple of practices are in place on the project. These two practices are prerequisites for applying the approach in this book.
1. 开发是迭代的。迭代开发已被倡导和实践数十年,它是敏捷开发方法的基石。关于敏捷开发和极限编程(或 XP)的文献中有很多优秀的讨论,其中包括《面向对象项目生存指南》(Cockburn,1998)和《极限编程详解》(Beck,1999)。
1. Development is iterative. Iterative development has been advocated and practiced for decades, and it is a cornerstone of Agile development methods. There are many good discussions in the literature of Agile development and Extreme Programming (or XP), among them, Surviving Object-Oriented Projects (Cockburn 1998) and Extreme Programming Explained (Beck 1999).
2. 开发人员和领域专家关系密切。领域驱动设计将大量知识整合到一个模型中,该模型体现了对领域的深刻洞察,并聚焦于关键概念。这是了解领域的人员和懂得如何构建软件的人员之间的合作。由于开发是一个迭代的过程,这种合作必须贯穿项目的整个生命周期。
2. Developers and domain experts have a close relationship. Domain-driven design crunches a huge amount of knowledge into a model that reflects deep insight into the domain and a focus on the key concepts. This is a collaboration between those who know the domain and those who know how to build software. Because development is iterative, this collaboration must continue throughout the project’s life.
极限编程(Extreme Programming,XP)由肯特·贝克(Kent Beck)、沃德·坎宁安(Ward Cunningham)等人提出(参见《极限编程详解》 [ Beck 2000 ]),是敏捷流程中最杰出的,也是我接触最多的流程。本书中,为了使解释更加具体,我将以XP为基础,探讨设计与流程的交互。书中阐述的原则也很容易应用于其他敏捷流程。
Extreme Programming, conceived by Kent Beck, Ward Cunningham, and others (see Extreme Programming Explained [Beck 2000]), is the most prominent of the Agile processes and the one I have worked with most. Throughout this book, to make explanations concrete, I will use XP as the basis for discussion of the interaction of design and process. The principles illustrated are easily adapted to other Agile processes.
近年来,人们开始反感那些繁琐的开发方法,这些方法让项目背负了无用的静态文档和过度前期规划设计的重担。相反,敏捷流程(例如极限编程XP)强调应对变化和不确定性的能力。
In recent years there has been a rebellion against elaborate development methodologies that burden projects with useless, static documents and obsessive upfront planning and design. Instead, the Agile processes, such as XP, emphasize the ability to cope with change and uncertainty.
极限编程 (XP) 承认设计决策的重要性,但它强烈反对预先设计。相反,它致力于沟通,并努力提升项目快速调整方向的能力。凭借这种响应能力,开发人员可以在项目的任何阶段使用“最简单的可行方案”,然后不断重构,进行许多细微的设计改进,最终得到一个真正符合客户需求的设计。
Extreme Programming recognizes the importance of design decisions, but it strongly resists upfront design. Instead, it puts an admirable effort into communication and improving the project’s ability to change course rapidly. With that ability to react, developers can use the “simplest thing that could work” at any stage of a project and then continuously refactor, making many small design improvements, ultimately arriving at a design that fits the customer’s true needs.
这种极简主义恰好能有效缓解设计爱好者的一些过度行为。冗长繁琐、价值甚微的文件常常拖慢项目进度,导致“分析瘫痪”——团队成员害怕设计出现任何瑕疵,以至于毫无进展。这种情况必须改变。
This minimalism has been a much-needed antidote to some of the excesses of design enthusiasts. Projects have been bogged down by cumbersome documents that provided little value. They have suffered from “analysis paralysis,” with team members so afraid of an imperfect design that they made no progress at all. Something had to change.
不幸的是,这些流程理念中的一些可能会被误解。每个人对“最简单”的定义都不同。持续重构是一系列小的重新设计;缺乏扎实设计原则的开发人员会编写出难以理解或修改的代码库——这与敏捷性背道而驰。虽然对意外需求的担忧常常导致过度设计,但尝试……避免过度设计可能会演变成另一种恐惧:害怕进行任何深入的设计思考。
Unfortunately, some of these process ideas can be misinterpreted. Each person has a different definition of “simplest.” Continuous refactoring is a series of small redesigns; developers without solid design principles will produce a code base that is hard to understand or change—the opposite of agility. And although fear of unanticipated requirements often leads to overengineering, the attempt to avoid overengineering can develop into another fear: a fear of doing any deep design thinking at all.
事实上,极限编程(XP)最适合那些拥有敏锐设计意识的开发者。XP流程假设可以通过重构来改进设计,并且需要频繁且快速地进行重构。但过去的设计选择会影响重构的难易程度。XP流程旨在加强团队沟通,但模型和设计选择会影响沟通的清晰度或清晰度。
In fact, XP works best for developers with a sharp design sense. The XP process assumes that you can improve a design by refactoring, and that you will do this often and rapidly. But past design choices make refactoring itself either easier or harder. The XP process attempts to increase team communication, but model and design choices clarify or confuse communication.
本书将设计与开发实践紧密结合,阐述了领域驱动设计与敏捷开发如何相互促进。在敏捷开发流程的背景下,采用精细的领域建模方法能够加速开发进程。流程与领域开发的相互关联使得这种方法比任何孤立地探讨“纯粹”设计都更具实用性。
This book intertwines design and development practice and illustrates how domain-driven design and Agile development reinforce each other. A sophisticated approach to domain modeling within the context of an Agile development process will accelerate development. The interrelationship of process with domain development makes this approach more practical than any treatment of “pure” design in a vacuum.
本书分为四个主要部分:
The book is divided into four major sections:
第一部分:领域模型的应用,阐述了领域驱动开发的基本目标;这些目标为后续章节的实践提供了理论基础。由于软件开发方法众多,第一部分首先定义了相关术语,并概述了运用领域模型来驱动沟通和设计的意义。
Part I: Putting the Domain Model to Work presents the basic goals of domain-driven development; these goals motivate the practices in later sections. Because there are so many approaches to software development, Part I defines terms and gives an overview of the implications of using the domain model to drive communication and design.
第二部分:模型驱动设计的构建模块,将面向对象领域建模的最佳实践精简为一组基本构建模块。本部分重点在于弥合模型与实际运行软件之间的鸿沟。共享这些标准模式能够使设计更加规范,团队成员更容易理解彼此的工作。使用标准模式还有助于形成通用术语,供所有团队成员讨论模型和设计决策。
Part II: The Building Blocks of a Model-Driven Design condenses a core of best practices in object-oriented domain modeling into a set of basic building blocks. This section focuses on bridging the gap between models and practical, running software. Sharing these standard patterns brings order to the design. Team members more easily understand each other’s work. Using standard patterns also contributes terminology to a common language, which all team members can use to discuss model and design decisions.
但本节的重点在于探讨那些能够使模型和实施保持一致的决策,二者相辅相成,共同提升彼此的有效性。这种一致性这需要关注各个元素的细节。在这种小规模下精心制作,能为开发人员提供一个稳定的基础,以便应用第三部分和第四部分中的建模方法。
But the main point of this section is to focus on the kinds of decisions that keep the model and implementation aligned with each other, each reinforcing the other’s effectiveness. This alignment requires attention to the detail of individual elements. Careful crafting at this small scale gives developers a steady foundation from which to apply the modeling approaches of Parts III and IV.
第三部分:重构以求更深层次的洞察,超越了基础架构的构建,着重探讨如何将它们组装成能够带来实际收益的实用模型。本部分并非直接深入深奥的设计原则,而是强调探索过程。有价值的模型并非一蹴而就,它们需要对领域有深刻的理解。这种理解源于深入实践,首先基于一个可能略显稚嫩的模型实现初始设计,然后不断地对其进行改进和调整。每当团队获得新的洞察,模型就会进行相应的调整以展现更丰富的知识,代码也会进行重构以反映更深层次的模型,并使其潜力得以在应用程序中得到充分发挥。有时,这种层层剥开的探索会带来突破,从而获得一个更深层次的模型,并由此引发一系列意义深远的设计变革。
Part III: Refactoring Toward Deeper Insight goes beyond the building blocks to the challenge of assembling them into practical models that provide the payoff. Rather than jumping directly into esoteric design principles, this section emphasizes the discovery process. Valuable models do not emerge immediately; they require a deep understanding of the domain. That understanding comes from diving in, implementing an initial design based on a probably naive model, and then transforming it again and again. Each time the team gains insight, the model is transformed to reveal that richer knowledge, and the code is refactored to reflect the deeper model and make its potential available to the application. Then, once in a while, this onion peeling leads to an opportunity to break through to a much deeper model, attended by a rush of profound design changes.
探索本质上是开放式的,但并非必然是随机的。第三部分深入探讨了能够指导探索过程中各种选择的建模原则,以及有助于引导搜索的技巧。
Exploration is inherently open-ended, but it does not have to be random. Part III delves into modeling principles that can guide choices along the way, and techniques that help direct the search.
第四部分:战略设计,探讨复杂系统、大型组织以及与外部系统和遗留系统交互时出现的各种情况。本部分阐述了适用于整个系统的三个原则:情境、提炼和大规模结构。战略设计决策由团队制定,甚至在团队之间也可以协商制定。战略设计使第一部分的目标能够在更大的范围内得以实现,适用于大型系统或融入庞大企业级网络的应用程序。
Part IV: Strategic Design deals with situations that arise in complex systems, larger organizations, and interactions with external systems and legacy systems. This section explores a triad of principles that apply to the system as a whole: context, distillation, and large-scale structure. Strategic design decisions are made by teams, or even among teams. Strategic design enables the goals of Part I to be realized on a larger scale, for a big system or an application that fits into a sprawling, enterprise-wide network.
本书通篇采用的讨论方式并非使用过于简化的“玩具”问题,而是使用从实际项目中改编的现实示例。
Throughout the book, discussions are illustrated not with over-simplified, “toy” problems, but with realistic examples adapted from actual projects.
本书大部分内容都是以一系列“模式”的形式写成的。读者应该能够理解这些内容而无需担心这一点。虽然该设备本身并不复杂,但对图案的样式和格式感兴趣的人可能需要阅读附录。
Much of the book is written as a set of “patterns.” Readers should be able to understand the material without concern about this device, but those who are interested in the style and format of the patterns may want to read the appendix.
补充材料可在http://domaindrivendesign.org找到,包括其他示例代码和社区讨论。
Supplemental materials can be found at http://domaindrivendesign.org, including additional example code and community discussion.
本书主要面向面向对象软件开发人员。软件项目团队的大多数成员都能从本书的部分内容中获益。对于目前正在参与项目、尝试实践书中某些方法的人员,以及已经拥有丰富此类项目经验的人员来说,本书将最具参考价值。
This book is written primarily for developers of object-oriented software. Most members of a software project team can benefit from some parts of the book. It will make the most sense to people who are currently involved with a project, trying to do some of these things as they go through, and to people who already have deep experience with such projects.
要充分理解本书内容,需要具备一定的面向对象建模知识。书中示例包含 UML 图和 Java 代码,因此具备基本的 UML 和 Java 代码阅读能力很重要,但无需精通其中任何一种语言的细节。了解极限编程 (XP) 将有助于理解开发过程的讨论,但即使没有相关背景知识,本书内容也应该易于理解。
Some knowledge of object-oriented modeling is necessary to benefit from this book. The examples include UML diagrams and Java code, so the ability to read those languages at a basic level is important, but it is unnecessary to have mastered the details of either. Knowledge of Extreme Programming will add perspective to the discussions of development process, but the material should be understandable to those without background knowledge.
对于中级软件开发人员——即已经了解一些面向对象设计知识,并且可能读过一两本软件设计书籍的读者——本书将填补知识空白,并帮助他们理解对象建模如何在实际软件项目中应用。本书将帮助中级开发人员学习如何将复杂的建模和设计技能应用于实际问题。
For intermediate software developers—readers who already know something of object-oriented design and may have read one or two software design books—this book will fill in gaps and provide perspective on how object modeling fits into real life on a software project. The book will help intermediate developers learn to apply sophisticated modeling and design skills to practical problems.
高级或专家级软件开发人员会对本书提供的全面领域框架感兴趣。这种系统化的设计方法将帮助技术领导者引导团队朝着正确的方向前进。此外,本书通篇使用的统一术语也有助于高级开发人员与同行沟通。
Advanced or expert software developers will be interested in the book’s comprehensive framework for dealing with the domain. This systematic approach to design will help technical leaders guide their teams down this path. Also, the coherent terminology used throughout the book will help advanced developers communicate with their peers.
本书采用叙事结构,既可从头至尾阅读,也可从任意章节的开头阅读。不同背景的读者或许会选择不同的阅读路径,但我建议所有读者都从第一部分的引言以及第一章开始阅读。除此之外,本书的核心内容可能集中在第2、3、9和14章。对相关主题已有一定了解的读者,可以通过阅读标题和粗体字来把握要点。而资深读者可能只想略读第一部分和第二部分,并且可能对第三部分和第四部分最感兴趣。
This book is a narrative, and it can be read from beginning to end, or from the beginning of any chapter. Readers of various backgrounds may wish to take different paths through the book, but I do recommend that all readers start with the introduction to Part I, as well as Chapter 1. Beyond that, the core is probably Chapters 2, 3, 9, and 14. A skimmer who already has some grasp of a topic should be able to pick up the main points by reading headings and bold text. A very advanced reader may want to skim Parts I and II and will probably be most interested in Parts III and IV.
除了核心读者群之外,分析师和技术水平相对较高的项目经理也能从本书中获益。分析师可以利用模型与设计之间的联系,在敏捷项目中做出更有效的贡献。此外,分析师还可以运用战略设计的一些原则,更好地集中精力并组织工作。
In addition to this core readership, analysts and relatively technical project managers will also benefit from reading the book. Analysts can draw on the connection between model and design to make more effective contributions in the context of an Agile project. Analysts may also use some of the principles of strategic design to better focus and organize their work.
项目经理应该关注如何提高团队效率,使其更加专注于设计对业务专家和用户真正有意义的软件。由于战略设计决策与团队组织和工作方式息息相关,因此这些设计决策必然涉及项目领导层,并对项目的走向产生重大影响。
Project managers should be interested in the emphasis on making a team more effective and more focused on designing software meaningful to business experts and users. And because strategic design decisions are interrelated with team organization and work styles, these design decisions necessarily involve the leadership of the project and have a major impact on the project’s trajectory.
虽然理解领域驱动设计(DDD)的开发者个人能够获得宝贵的设计技巧和视角,但最大的收益来自于团队协作,共同应用DDD方法,并将领域模型置于项目讨论的核心。如此一来,团队成员将共享一种语言,从而丰富彼此的沟通,并使其与软件紧密相连。他们将根据模型构建清晰的实现,从而提升应用开发效率。他们将共享一张不同团队设计工作之间关联的地图,并系统地将注意力集中在对组织而言最具特色和最有价值的功能上。
Although an individual developer who understands domain-driven design will gain valuable design techniques and perspective, the biggest gains come when a team joins together to apply a domain-driven design approach and to move the domain model to the project’s center of discourse. By doing so, the team members will share a language that enriches their communication and keeps it connected to the software. They will produce a lucid implementation in step with a model, giving leverage to application development. They will share a map of how the design work of different teams relates, and they will systematically focus attention on the features that are most distinctive and valuable to the organization.
领域驱动设计是一项艰巨的技术挑战,但可以带来巨大的回报,在大多数软件项目开始僵化成遗留系统时,它能创造新的机遇。
Domain-driven design is a difficult technical challenge that can pay off big, opening opportunities just when most software projects begin to ossify into legacy.
四年来,我一直在以各种形式创作这本书,在此过程中,许多人都给予了我帮助和支持。
I have been working on this book, in one form or another, for more than four years, and many people have helped and supported me along the way.
我衷心感谢所有阅读过书稿并提出宝贵意见的人。没有你们的反馈,这本书根本不可能问世。其中一些人的审阅尤其详尽周到。由Russ Rufer和Tracy Bialek领导的硅谷模式小组花了七周时间仔细审阅了本书的第一稿。由Ralph Johnson领导的伊利诺伊大学读书小组也花了数周时间审阅了后续的草稿。聆听这些小组长时间而热烈的讨论对我影响深远。Kyle Brown和Martin Fowler提供了详尽的反馈、宝贵的见解以及无比重要的精神支持(尽管他们当时正坐在鱼背上)。Ward Cunningham的评论帮助我弥补了一些重要的不足之处。Alistair Cockburn和Hilary Evans在早期就给予了我鼓励,并帮助我顺利完成了出版流程。David Siegel和Eugene Wallingford帮助我避免在技术性较强的部分出现纰漏。Vibhu Mohindra和Vladimir Gitlevich一丝不苟地检查了所有的代码示例。
I thank those people who have read manuscripts and commented. This book would simply not have been possible without that feedback. A few have given their reviews especially generous attention. The Silicon Valley Patterns Group, led by Russ Rufer and Tracy Bialek, spent seven weeks scrutinizing the first complete draft of the book. The University of Illinois reading group led by Ralph Johnson also spent several weeks reviewing a later draft. Listening to the long, lively discussions of these groups had a profound effect. Kyle Brown and Martin Fowler contributed detailed feedback, valuable insights, and invaluable moral support (while sitting on a fish). Ward Cunningham’s comments helped me shore up some important weak points. Alistair Cockburn encouraged me early on and helped me find my way through the publication process, as did Hilary Evans. David Siegel and Eugene Wallingford have helped me avoid embarrassing myself in the more technical parts. Vibhu Mohindra and Vladimir Gitlevich painstakingly checked all the code examples.
罗布·米阅读了我早期对这种材料的探索,并在我苦苦思索如何表达这种设计风格时与我一起集思广益。之后,他又和我一起仔细研读了后期的草稿。
Rob Mee read some of my earliest explorations of the material, and brainstormed ideas with me when I was groping for some way to communicate this style of design. He then pored over a much later draft with me.
乔什·克里耶夫斯基是本书发展过程中一个重要的转折点:他劝说我尝试“亚历山大式”结构,这后来成为本书组织结构的核心。在密集的“指导”过程中,他还帮助我首次将第二部分中的一些材料整合为一个连贯的形式。这是 1999 年 PLoP 会议之前的一个过程。这成为了本书其余部分形成的种子。
Josh Kerievsky is responsible for one of the major turning points in the book’s development: He persuaded me to try out the “Alexandrian” pattern format, which became so central to the book’s organization. He also helped me to bring together some of the material now in Part II into a coherent form for the first time, during the intensive “shepherding” process preceding the PLoP conference in 1999. This became a seed around which much of the rest of the book formed.
我还要感谢阿瓦德·法杜尔,我曾在他那间美妙的咖啡馆里写作数百小时。那次静修,再加上大量的帆板运动,帮助我坚持了下来。
Also I thank Awad Faddoul for the hundreds of hours I sat writing in his wonderful café. That retreat, along with a lot of windsurfing, helped me keep going.
我非常感谢 Martine Jousset、Richard Paselk 和 Ross Venables 创作了一些精美的照片来说明一些关键概念(参见第517页的照片版权信息)。
And I’m very grateful to Martine Jousset, Richard Paselk, and Ross Venables for creating some beautiful photographs to illustrate a few key concepts (see photo credits on page 517).
在构思这本书之前,我必须先形成自己对软件开发的观点和理解。这很大程度上要归功于几位才华横溢的人,他们既是我的非正式导师,也是我的朋友。David Siegel、Eric Gold 和 Iseult White 以各自不同的方式帮助我发展了软件设计的思维方式。与此同时,Bruce Gordon、Richard Freyberg 和 Judith Segal 博士也以截然不同的方式,帮助我在成功的项目工作中找到方向。
Before I could have conceived of this book, I had to form my view and understanding of software development. That formation owed a lot to the generosity of a few brilliant people who acted as informal mentors to me, as well as friends. David Siegel, Eric Gold, and Iseult White, each in a different way, helped me develop my way of thinking about software design. Meanwhile, Bruce Gordon, Richard Freyberg, and Dr. Judith Segal, also in very different ways, helped me find my way in the world of successful project work.
我的想法自然而然地源于当时涌现的各种思想。其中一些影响会在正文中清晰阐述,并尽可能地加以引用。而另一些影响则如此根本,以至于我甚至没有意识到它们对我的影响。
My own notions naturally grew out of a body of ideas in the air at that time. Some of those contributions will be clear in the main text and referenced where possible. Others are so fundamental that I don’t even realize their influence on me.
我的硕士论文导师巴拉·苏布拉马尼亚姆博士引领我走进了数学建模的世界,我们将其应用于化学反应动力学研究。建模就是建模,而这项工作正是我创作本书的起点之一。
My master’s thesis advisor, Dr. Bala Subramanium, turned me on to mathematical modeling, which we applied to chemical reaction kinetics. Modeling is modeling, and that work was part of the path that led to this book.
甚至在此之前,我的思维方式就受到了我的父母卡罗尔和加里·埃文斯的影响。还有几位特别的老师激发了我的兴趣或帮助我打下了基础,特别是戴尔·柯里尔(一位高中数学老师)、玛丽·布朗(一位高中英语作文老师)和约瑟芬·麦格拉默里(一位六年级科学老师)。
Even before that, my way of thinking was shaped by my parents, Carol and Gary Evans. And a few special teachers awakened my interest or helped me lay foundations, especially Dale Currier (a high school math teacher), Mary Brown (a high school English composition teacher), and Josephine McGlamery (a sixth-grade science teacher).
最后,我要感谢我的朋友、家人以及费尔南多·德·莱昂,感谢他们一路以来的鼓励。
Finally, I thank my friends and family, and Fernando De Leon, for their encouragement all along the way.
这幅十八世纪的中国地图描绘了整个世界。地图中心占据了大部分空间的是中国,其他国家则被粗略地描绘在周围。这是一种符合当时社会风貌的世界观,而当时的社会有意地封闭内向。地图所呈现的世界观显然不利于与外国人交往,当然也完全不适用于现代中国。地图是一种模型,而任何模型都代表着现实的某个方面或某种人们感兴趣的理念。模型是一种简化,它对现实进行诠释,提取出与解决当前问题相关的方面,而忽略无关的细节。
This eighteenth-century Chinese map represents the whole world. In the center and taking up most of the space is China, surrounded by perfunctory representations of other countries. This was a model of the world appropriate to that society, which had intentionally turned inward. The worldview that the map represents must not have been helpful in dealing with foreigners. Certainly it would not serve modern China at all. Maps are models, and every model represents some aspect of reality or an idea that is of interest. A model is a simplification. It is an interpretation of reality that abstracts the aspects relevant to solving the problem at hand and ignores extraneous detail.
每个软件程序都与其用户的某种活动或兴趣相关。用户应用该程序的领域就是该软件的领域。有些领域涉及物理世界:例如,航空公司订票程序的领域涉及真实的人登上真实的飞机。有些领域是无形的:例如,会计程序的领域是金钱和财务。软件领域通常与计算机关系不大,但也有例外:例如,源代码控制系统的领域就是软件开发本身。
Every software program relates to some activity or interest of its user. That subject area to which the user applies the program is the domain of the software. Some domains involve the physical world: The domain of an airline-booking program involves real people getting on real aircraft. Some domains are intangible: The domain of an accounting program is money and finance. Software domains usually have little to do with computers, though there are exceptions: The domain of a source-code control system is software development itself.
为了开发出能够真正融入用户活动的软件,开发团队必须掌握与这些活动相关的丰富知识。所需知识的广度可能令人望而生畏,信息的数量和复杂性也可能让人不知所措。模型是应对这种信息过载的工具。模型是一种经过选择性简化和有意识结构化的知识形式。合适的模型能够理解信息并将其聚焦于问题本身。
To create software that is valuably involved in users’ activities, a development team must bring to bear a body of knowledge related to those activities. The breadth of knowledge required can be daunting. The volume and complexity of information can be overwhelming. Models are tools for grappling with this overload. A model is a selectively simplified and consciously structured form of knowledge. An appropriate model makes sense of information and focuses it on a problem.
领域模型并非指某个特定的图表,而是指该图表旨在传达的理念。它并非仅仅是领域专家头脑中的知识,而是对这些知识进行严谨组织和选择性提炼后的抽象。图表可以表示和传达模型,精心编写的代码和英文句子也同样可以。
A domain model is not a particular diagram; it is the idea that the diagram is intended to convey. It is not just the knowledge in a domain expert’s head; it is a rigorously organized and selective abstraction of that knowledge. A diagram can represent and communicate a model, as can carefully written code, as can an English sentence.
领域建模并非旨在构建尽可能“逼真”的模型。即使在现实世界中存在有形事物的领域,我们的模型也是一种人为创造。它并非仅仅是构建一个能够产生所需结果的软件机制。它更像是电影制作,为了特定目的而对现实进行粗略的呈现。即使是纪录片也不会展现未经剪辑的真实生活。正如电影制作人会选择经验的各个方面,并以独特的方式呈现出来以讲述故事或阐明观点一样,领域建模者也会根据模型的实用性来选择特定的模型。
Domain modeling is not a matter of making as “realistic” a model as possible. Even in a domain of tangible real-world things, our model is an artificial creation. Nor is it just the construction of a software mechanism that gives the necessary results. It is more like moviemaking, loosely representing reality to a particular purpose. Even a documentary film does not show unedited real life. Just as a moviemaker selects aspects of experience and presents them in an idiosyncratic way to tell a story or make a point, a domain modeler chooses a particular model for its utility.
在领域驱动设计中,三种基本用途决定了模型的选择。
In domain-driven design, three basic uses determine the choice of a model.
1. 模型与设计的核心相互塑造。正是模型与实现之间的紧密联系,使得模型具有现实意义,并确保模型分析能够应用于最终产品——一个可运行的程序。这种模型与实现的结合也有助于维护和持续开发,因为代码可以基于对模型的理解进行解释。(参见第3章。)
1. The model and the heart of the design shape each other. It is the intimate link between the model and the implementation that makes the model relevant and ensures that the analysis that went into it applies to the final product, a running program. This binding of model and implementation also helps during maintenance and continuing development, because the code can be interpreted based on understanding the model. (See Chapter 3.)
2. 该模型是所有团队成员使用的语言的骨干。由于模型与实现紧密结合,开发人员可以用这种语言讨论程序。他们可以与领域专家进行交流,而无需翻译。而且,由于这种语言基于模型,我们可以利用自身的语言天赋来改进模型本身。(参见第二章。)
2. The model is the backbone of a language used by all team members. Because of the binding of model and implementation, developers can talk about the program in this language. They can communicate with domain experts without translation. And because the language is based on the model, our natural linguistic abilities can be turned to refining the model itself. (See Chapter 2.)
3. 模型是知识的提炼。模型是团队共同认可的领域知识结构化方式,它能帮助我们区分最关键的要素。模型体现了我们如何思考领域,包括选择术语、分解概念以及将它们关联起来。这种共享的语言使开发人员和领域专家能够高效协作,共同将信息整合到模型中。模型与实现的结合使得早期软件版本的经验可以作为反馈应用于建模过程。(参见第1章。)
3. The model is distilled knowledge. The model is the team’s agreed-upon way of structuring domain knowledge and distinguishing the elements of most interest. A model captures how we choose to think about the domain as we select terms, break down concepts, and relate them. The shared language allows developers and domain experts to collaborate effectively as they wrestle information into this form. The binding of model and implementation makes experience with early versions of the software applicable as feedback into the modeling process. (See Chapter 1.)
接下来的三章将依次探讨这些贡献的意义和价值,以及它们之间的相互联系。以这种方式运用模型可以支持开发功能丰富的软件,而这些软件如果采用其他方式开发,则需要投入大量资源进行临时开发。
The next three chapters set out to examine the meaning and value of each of these contributions in turn, and the ways they are intertwined. Using a model in these ways can support the development of software with rich functionality that would otherwise take a massive investment of ad hoc development.
软件的核心在于其为用户解决领域相关问题的能力。所有其他功能,无论多么重要,都服务于这一基本目标。当领域复杂时,这项任务就变得异常艰巨,需要才华横溢、技能精湛的人员集中精力。开发人员必须深入了解领域,积累业务知识。他们必须磨练建模技能,并掌握领域设计。
The heart of software is its ability to solve domain-related problems for its user. All other features, vital though they may be, support this basic purpose. When the domain is complex, this is a difficult task, calling for the concentrated effort of talented and skilled people. Developers have to steep themselves in the domain to build up knowledge of the business. They must hone their modeling skills and master domain design.
然而,这些并非大多数软件项目的优先事项。大多数才华横溢的开发人员对学习他们所从事的具体领域知识兴趣不大,更不用说投入大量精力来扩展他们的领域建模技能了。技术人员喜欢能够运用自身技术技能的可量化问题。领域工作繁杂且需要大量复杂的新知识,这些知识似乎并不能提升计算机科学家的能力。
Yet these are not the priorities on most software projects. Most talented developers do not have much interest in learning about the specific domain in which they are working, much less making a major commitment to expand their domain-modeling skills. Technical people enjoy quantifiable problems that exercise their technical skills. Domain work is messy and demands a lot of complicated new knowledge that doesn’t seem to add to a computer scientist’s capabilities.
相反,技术人才却投入到复杂的框架构建中,试图用技术解决领域问题。而对领域的学习和建模则留给了其他人。软件核心的复杂性必须正面应对,否则就有可能被时代淘汰。
Instead, the technical talent goes to work on elaborate frameworks, trying to solve domain problems with technology. Learning about and modeling the domain is left to others. Complexity in the heart of software has to be tackled head-on. To do otherwise is to risk irrelevance.
在一次电视访谈节目中,喜剧演员约翰·克里斯讲述了拍摄《巨蟒与圣杯》期间发生的一件事。他们反复拍摄一个场景,但不知为何总是不好笑。最后,他停下来和另一位喜剧演员迈克尔·帕林(也是这场戏的演员)商量,两人想出了一个略微改动的版本。他们又拍了一遍,结果效果出奇的好,于是就收工了。
In a TV talk show interview, comedian John Cleese told a story of an event during the filming of Monty Python and the Holy Grail. They had been shooting a particular scene over and over, but somehow it wasn’t funny. Finally, he took a break and consulted with fellow comedian Michael Palin (the other actor in the scene), and they came up with a slight variation. They shot one more take, and it turned out funny, so they called it a day.
第二天早上,克里斯正在看剪辑师把前一天拍的素材粗剪出来的版本。看到他们之前费了好大劲才剪出来的那场戏时,克里斯发现它一点也不好笑;原来是用了之前拍的某个镜头。
The next morning, Cleese was looking at the rough cut the film editor had put together of the previous day’s work. Coming to the scene they had struggled with, Cleese found that it wasn’t funny; one of the earlier takes had been used.
他问剪辑师为什么没按指示用最后一条。“用不了。有人走进镜头了。”剪辑师回答。克里斯又看了一遍,又一遍。他还是没发现任何问题。最后,剪辑师停下影片,指着画面边缘一闪而过的衣袖。
He asked the film editor why he hadn’t used the last take, as directed. “Couldn’t use it. Someone walked in-shot,” the editor replied. Cleese watched the scene again, and then again. Still he could see nothing wrong. Finally, the editor stopped the film and pointed out a coat sleeve that was visible for a moment at the edge of the picture.
这位剪辑师专注于精准地完成自己的专业工作。他担心其他看过这部电影的剪辑师会根据技术上的完美程度来评判他的作品。结果,这场戏的精髓却丢失了(《克雷格·基尔伯恩深夜秀》,CBS,2001年9月)。
The film editor was focused on the precise execution of his own specialty. He was concerned that other film editors who saw the movie would judge his work based on its technical perfection. In the process, the heart of the scene had been lost (“The Late Late Show with Craig Kilborn,” CBS, September 2001).
幸运的是,一位懂喜剧的导演恢复了这段搞笑的场景。同样,团队中那些理解领域核心地位的领导者,也能在构建反映深刻理解的模型过程中出现偏差时,使软件项目重回正轨。
Fortunately, the funny scene was restored by a director who understood comedy. In just the same way, leaders within a team who understand the centrality of the domain can put their software project back on course when development of a model that reflects deep understanding gets lost in the shuffle.
本书将展示领域开发蕴藏着培养精湛设计技能的机会。大多数领域开发都存在混乱的问题。软件领域实际上是一个有趣的技术挑战。事实上,在许多科学领域,“复杂性”是当前最令人兴奋的话题之一,因为研究人员试图解决现实世界的复杂性问题。软件开发人员在面对一个从未被形式化的复杂领域时,也面临着同样的挑战。创建一个能够化繁为简、清晰易懂的模型,无疑令人兴奋。
This book will show that domain development holds opportunities to cultivate very sophisticated design skills. The messiness of most software domains is actually an interesting technical challenge. In fact, in many scientific disciplines, “complexity” is one of the most exciting current topics, as researchers attempt to tackle the messiness of the real world. A software developer has that same prospect when facing a complicated domain that has never been formalized. Creating a lucid model that cuts through that complexity is exciting.
开发者可以运用系统性的思维方式来寻找洞见并构建有效的模型。一些设计技巧可以帮助开发者理清庞大的软件应用。培养这些技能能够显著提升开发者的价值,即使是在最初并不熟悉的领域。
There are systematic ways of thinking that developers can employ to search for insight and produce effective models. There are design techniques that can bring order to a sprawling software application. Cultivation of these skills makes a developer much more valuable, even in an initially unfamiliar domain.
几年前,我着手设计一款专门用于印刷电路板 (PCB) 设计的软件工具。但问题是:我对电子硬件一窍不通。当然,我能接触到一些 PCB 设计师,但他们通常三分钟之内就能把我搞得晕头转向。我怎么可能理解到足以编写这款软件的程度呢?我肯定不可能在交付期限前成为一名电子工程师!
A few years ago, I set out to design a specialized software tool for printed-circuit board (PCB) design. One catch: I didn’t know anything about electronic hardware. I had access to some PCB designers, of course, but they typically got my head spinning in three minutes. How was I going to understand enough to write this software? I certainly wasn’t going to become an electrical engineer before the delivery deadline!
我们曾尝试让PCB设计师直接告诉我软件应该实现什么功能。结果证明这是个糟糕的主意。他们的确是优秀的电路设计师,但他们对软件的设想通常是:读取ASCII文件,进行排序,添加一些注释后再写回文件,最后生成一份报告。显然,这无法带来他们所期望的生产力飞跃。
We tried having the PCB designers tell me exactly what the software should do. Bad idea. They were great circuit designers, but their software ideas usually involved reading in an ASCII file, sorting it, writing it back out with some annotation, and producing a report. This was clearly not going to lead to the leap forward in productivity that they were looking for.
最初几次会议令人沮丧,但他们要求的报告却带来了一线希望。这些报告总是涉及“网络”以及相关的各种细节。在这个领域,“网络”本质上是一种导线,它可以连接PCB上的任意数量的元件,并将电信号传输给所有连接的元件。我们获得了领域模型的第一个要素。
The first few meetings were discouraging, but there was a glimmer of hope in the reports they asked for. They always involved “nets” and various details about them. A net, in this domain, is essentially a wire conductor that can connect any number of components on a PCB and carry an electrical signal to everything it is connected to. We had the first element of the domain model.
图 1.1
Figure 1.1
在讨论他们希望软件实现的功能时,我开始为他们绘制图表。我使用了一种非正式的对象交互图来逐步演示各种场景。
I started drawing diagrams for them as we discussed the things they wanted the software to do. I used an informal variant of object interaction diagrams to walk through scenarios.
图 1.2
Figure 1.2
PCB专家1:这些元件不一定是芯片。
PCB Expert 1: The components wouldn’t have to be chips.
开发者(我):那我应该直接称它们为“组件”吗?
Developer (Me): So I should just call them “components”?
专家1:我们称它们为“组件实例”。同一个组件可以有多个实例。
Expert 1: We call them “component instances.” There could be many of the same component.
专家 2: “网络”框看起来就像一个组件实例。
Expert 2: The “net” box looks just like a component instance.
专家1:他没用我们的符号系统。我猜对他们来说,什么都是一个方框。
Expert 1: He’s not using our notation. Everything is a box for them, I guess.
开发者:很遗憾,是的。我想我最好再详细解释一下这种符号。
Developer: Sorry to say, yes. I guess I’d better explain this notation a little more.
他们不断纠正我的错误,我也在过程中不断学习。我们一起厘清了他们术语上的冲突和歧义,以及他们技术观点上的差异,他们也从中受益匪浅。他们开始更精准、更一致地解释问题,我们也开始共同构建模型。
They constantly corrected me, and as they did I started to learn. We ironed out collisions and ambiguities in their terminology and differences between their technical opinions, and they learned. They began to explain things more precisely and consistently, and we started to develop a model together.
专家 1:仅仅说信号到达参考设备是不够的,我们必须知道引脚。
Expert 1: It isn’t enough to say a signal arrives at a ref-des, we have to know the pin.
开发者:参考设计?
Developer: Ref-des?
专家2:这和组件实例是一样的。在我们用的一个特定工具里,它被称为Ref-des。
Expert 2: Same thing as a component instance. Ref-des is what it’s called in a particular tool we use.
专家 1:总之,网络将一个实例的特定引脚连接到另一个实例的特定引脚。
Expert 1: Anyhow, a net connects a particular pin of one instance to a particular pin of another.
开发者:你是说一个引脚只属于一个组件实例,并且只连接到一个网络吗?
Developer: Are you saying that a pin belongs to only one component instance and connects to only one net?
专家 2:此外,每个网络都有拓扑结构,即决定网络中各元素连接方式的排列方式。
Expert 2: Also, every net has a topology, an arrangement that determines the way the elements of the net connect.
开发者:好的,这样可以吗?
Developer: OK, how about this?
图 1.3
Figure 1.3
为了集中研究方向,我们暂时将研究范围限定在一个特定特征上。“探测模拟”会追踪信号的传播,以检测设计中可能存在的特定问题。
To focus our exploration, we limited ourselves, for a while, to studying one particular feature. A “probe simulation” would trace the propagation of a signal to detect likely sites of certain kinds of problems in the design.
开发人员:我理解信号如何通过网络传输到所有连接的引脚,但除此之外它是如何传输的呢?拓扑结构与此有关吗?
Developer: I understand how the signal gets carried by the Net to all the Pins attached, but how does it go any further than that? Does the Topology have something to do with it?
专家2:不。该组件负责传输信号。
Expert 2: No. The component pushes the signal through.
开发人员:我们当然无法模拟芯片的内部行为。那太复杂了。
Developer: We certainly can’t model the internal behavior of a chip. That’s way too complicated.
专家2:我们不必这么做。我们可以简化一下。只需列出元件上从某些引脚到其他引脚的推送操作即可。
Expert 2: We don’t have to. We can use a simplification. Just a list of pushes through the component from certain Pins to certain others.
开发者:类似这样的吗?
Developer: Something like this?
经过反复尝试,我们共同勾勒出一个方案。
[With considerable trial-and-error, together we sketched out a scenario.]
图 1.4
Figure 1.4
Developer: But what exactly do you need to know from this computation?
专家2:我们会寻找较长的信号延迟——例如,任何超过两到三跳的信号路径。这是一条经验法则。如果路径太长,信号可能无法在时钟周期内到达。
Expert 2: We’d be looking for long signal delays—say, any signal path that was more than two or three hops. It’s a rule of thumb. If the path is too long, the signal may not arrive during the clock cycle.
开发者:超过三跳……所以我们需要计算路径长度。那么,什么才算一跳呢?
Developer: More than three hops.... So we need to calculate the path lengths. And what counts as a hop?
专家 2:信号每次经过网络,就是一跳。
Expert 2: Each time the signal goes over a Net, that’s one hop.
开发者:所以我们可以传递跳数,然后网络可以像这样递增它。
Developer: So we could pass the number of hops along, and a Net could increment it, like this.
图 1.5
Figure 1.5
开发者:我唯一不清楚的地方是“推送”数据来自哪里。我们是否需要为每个组件实例存储这些数据?
Developer: The only part that isn’t clear to me is where the “pushes” come from. Do we store that data for every Component Instance?
专家 2:组件的所有实例的推送操作都相同。
Expert 2: The pushes would be the same for all the instances of a component.
开发者:所以组件类型决定了推送操作。所有实例的推送操作都一样吗?
Developer: So the type of component determines the pushes. They’ll be the same for every instance?
图 1.6
Figure 1.6
专家 2:我不太确定其中一些具体是什么意思,但我认为存储每个组件的推入信息应该类似于这样。
Expert 2: I’m not sure exactly what some of this means, but I would imagine storing push-throughs for each component would look something like that.
开发者:抱歉,我刚才说得太详细了。我只是在仔细思考……那么,拓扑结构在这里起什么作用呢?
Developer: Sorry, I got a little too detailed there. I was just thinking it through. . . . So, now, where does the Topology come into it?
专家 1:探针模拟中不用到这个。
Expert 1: That’s not used for the probe simulation.
开发者:那我暂时先把它搁置一下,好吗?等我们开发出那些功能的时候再把它加回来。
Developer: Then I’m going to drop it out for now, OK? We can bring it back when we get to those features.
事情就这样一步步发展下去(实际过程中磕磕绊绊远比这里展现的要多)。我们集思广益,不断完善;提出问题,反复解释。模型的构建伴随着我对领域的理解以及他们对模型如何融入解决方案的理解而不断推进。早期模型的类图大致如下所示。
And so it went (with much more stumbling than is shown here). Brainstorming and refining; questioning and explaining. The model developed along with my understanding of the domain and their understanding of how the model would play into the solution. A class diagram representing that early model looks something like this.
图 1.7
Figure 1.7
经过几天的兼职学习,我觉得自己理解得足够透彻,可以尝试编写一些代码了。我写了一个非常简单的原型,由一个自动化测试框架驱动。我没有使用任何基础设施,没有持久化存储,也没有用户界面(UI)。这让我可以专注于程序的行为。仅仅几天后,我就演示了一个简单的探测模拟。虽然它使用了虚拟数据,并将原始文本输出到控制台,但它实际上是使用 Java 对象来计算路径长度的。这些 Java 对象反映了我和领域专家共同认可的模型。
After a couple more part-time days of this, I felt I understood enough to attempt some code. I wrote a very simple prototype, driven by an automated test framework. I avoided all infrastructure. There was no persistence, and no user interface (UI). This allowed me to concentrate on the behavior. I was able to demonstrate a simple probe simulation in just a few more days. Although it used dummy data and wrote raw text to the console, it was nonetheless doing the actual computation of path lengths using Java objects. Those Java objects reflected a model shared by the domain experts and myself.
这个原型具体化的设计让领域专家更清楚地理解了模型的含义以及它与实际软件运行之间的关系。从那时起,我们对模型的讨论变得更加互动。因为他们可以看到我是如何将新获得的知识融入模型,然后再融入软件中的。而且,他们还能从原型中获得具体的反馈,从而评估自己的想法。
The concreteness of this prototype made clearer to the domain experts what the model meant and how it related to the functioning software. From that point, our model discussions became more interactive, as they could see how I incorporated my newly acquired knowledge into the model and then into the software. And they had concrete feedback from the prototype to evaluate their own thoughts.
该模型自然比此处所示的要复杂得多,其中蕴含着与我们正在解决的问题相关的PCB领域知识。它整合了许多同义词和描述上的细微差别,并排除了工程师们理解但并非直接相关的数百个事实,例如元件的实际数字特性。像我这样的软件专家只需查看图表,几分钟内就能掌握软件的功能。他/她将拥有一个框架来组织新信息,更快地学习,更好地判断哪些信息重要、哪些信息不重要,并更好地与PCB工程师沟通。
Embedded in that model, which naturally became much more complicated than the one shown here, was knowledge about the domain of PCB relevant to the problems we were solving. It consolidated many synonyms and slight variations in descriptions. It excluded hundreds of facts that the engineers understood but that were not directly relevant, such as the actual digital features of the components. A software specialist like me could look at the diagrams and in minutes start to get a grip on what the software was about. He or she would have a framework to organize new information and learn faster, to make better guesses about what was important and what was not, and to communicate better with the PCB engineers.
当工程师们描述他们需要的新功能时,我让他们演示对象如何交互。当模型对象无法满足我们重要的场景需求时,我们会集思广益,提出新的场景或修改旧场景,并充分运用他们的知识。我们不断完善模型,代码也随之演进。几个月后,PCB工程师们拥有了一个功能强大的工具,其效果远超预期。
As the engineers described new features they needed, I made them walk me through scenarios of how the objects interacted. When the model objects couldn’t carry us through an important scenario, we brainstormed new ones or changed old ones, crunching their knowledge. We refined the model; the code coevolved. A few months later the PCB engineers had a rich tool that exceeded their expectations.
我们采取的一些措施促成了我刚才描述的成功。
Certain things we did led to the success I just described.
1. 将模型与实现结合起来。这个粗糙的原型很早就建立了必要的联系,并在随后的所有迭代中都得到了保持。
1. Binding the model and the implementation. That crude prototype forged the essential link early, and it was maintained through all subsequent iterations.
2. 基于模型构建语言。起初,工程师们需要向我解释基本的PCB问题,而我需要解释类图的含义。但随着项目的推进,我们每个人都可以直接从模型中提取术语,并按照模型的结构将它们组织成句子,无需翻译即可清晰理解。
2. Cultivating a language based on the model. At first, the engineers had to explain elementary PCB issues to me, and I had to explain what a class diagram meant. But as the project proceeded, any of us could take terms straight out of the model, organize them into sentences consistent with the structure of the model, and be unambiguously understood without translation.
3. 开发知识丰富的模型。这些对象具有行为并强制执行规则。该模型不仅仅是一个数据模式;它它对于解决复杂问题至关重要。它包含了各种类型的知识。
3. Developing a knowledge-rich model. The objects had behavior and enforced rules. The model wasn’t just a data schema; it was integral to solving a complex problem. It captured knowledge of various kinds.
4. 模型精简。随着模型的完善,一些重要的概念被添加到模型中,但同样重要的是,一些被证明无用或不核心的概念也被剔除。当一个不必要的概念与一个必要的概念关联起来时,我们会找到一个新的模型,该模型能够区分出核心概念,从而剔除其他不必要的概念。
4. Distilling the model. Important concepts were added to the model as it became more complete, but equally important, concepts were dropped when they didn’t prove useful or central. When an unneeded concept was tied to one that was needed, a new model was found that distinguished the essential concept so that the other could be dropped.
5. 头脑风暴和实验。语言运用、草图绘制以及头脑风暴的态度,将我们的讨论变成了模型实验室,数百种实验方案得以尝试、检验和评估。随着团队推演各种场景,口头表达本身就为所提出的模型提供了快速的可行性测试,因为耳朵可以迅速分辨出表达的清晰度和流畅度,或者生硬感。
5. Brainstorming and experimenting. The language, combined with sketches and a brainstorming attitude, turned our discussions into laboratories of the model, in which hundreds of experimental variations could be exercised, tried, and judged. As the team went through scenarios, the spoken expressions themselves provided a quick viability test of a proposed model, as the ear could quickly detect either the clarity and ease or the awkwardness of expression.
正是头脑风暴和大规模实验的创造力,通过基于模型的语言加以利用,并通过实施过程中的反馈循环加以规范,才使得找到并提炼出一个知识丰富的模型成为可能。这种知识的提炼过程将团队的知识转化为有价值的模型。
It is the creativity of brainstorming and massive experimentation, leveraged through a model-based language and disciplined by the feedback loop through implementation, that makes it possible to find a knowledge-rich model and distill it. This kind of knowledge crunching turns the knowledge of the team into valuable models.
金融分析师处理大量数据。他们仔细查阅海量详细数据,反复组合,寻找其内在含义,力求用简洁的方式呈现真正重要的信息——一种可以作为财务决策依据的理解。
Financial analysts crunch numbers. They sift through reams of detailed figures, combining and recombining them looking for the underlying meaning, searching for a simple presentation that brings out what is really important—an understanding that can be the basis of a financial decision.
高效的领域建模师都是知识的收集者。他们从海量信息中提炼出相关的关键信息。他们不断尝试各种组织方法,寻找能够概括所有信息的简洁视角。许多模型被尝试、否定或修改。最终,成功源于一套能够解释所有细节的抽象概念的涌现。这种提炼是对已发现的最相关知识的严谨表达。
Effective domain modelers are knowledge crunchers. They take a torrent of information and probe for the relevant trickle. They try one organizing idea after another, searching for the simple view that makes sense of the mass. Many models are tried and rejected or transformed. Success comes in an emerging set of abstract concepts that makes sense of all the detail. This distillation is a rigorous expression of the particular knowledge that has been found most relevant.
知识整合并非孤军奋战。通常由开发人员领导的团队,汇集开发人员和领域专家,共同协作。他们收集信息并将其转化为有用的形式。原始素材来源于领域专家的智慧、现有系统的用户、技术团队在相关遗留系统或同一领域其他项目上的经验。这些素材以项目文档、业务文档以及大量的讨论形式呈现。早期版本或原型会将经验反馈给团队,并改变他们对信息的理解。
Knowledge crunching is not a solitary activity. A team of developers and domain experts collaborate, typically led by developers. Together they draw in information and crunch it into a useful form. The raw material comes from the minds of domain experts, from users of existing systems, from the prior experience of the technical team with a related legacy system or another project in the same domain. It comes in the form of documents written for the project or used in the business, and lots and lots of talk. Early versions or prototypes feed experience back into the team and change interpretations.
在传统的瀑布式开发方法中,业务专家与分析师沟通,分析师进行分析、提炼,然后将结果传递给程序员,由程序员编写软件。这种方法的失败之处在于它完全缺乏反馈。分析师完全负责创建模型,而模型的构建仅基于业务专家的输入。他们没有机会向程序员学习,也无法获得早期软件版本的经验。知识单向流动,无法积累。
In the old waterfall method, the business experts talk to the analysts, and analysts digest and abstract and pass the result along to the programmers, who code the software. This approach fails because it completely lacks feedback. The analysts have full responsibility for creating the model, based only on input from the business experts. They have no opportunity to learn from the programmers or gain experience with early versions of software. Knowledge trickles in one direction, but does not accumulate.
其他项目也采用迭代流程,但由于缺乏抽象,它们无法积累知识。开发人员让专家描述所需功能,然后着手开发。他们向专家展示成果,询问下一步该做什么。如果程序员练习重构,就能保持软件足够简洁,以便持续扩展;但如果程序员对领域知识不感兴趣,他们就只能学习应用程序应该做什么,而无法理解其背后的原理。这种方法也能开发出有用的软件,但项目永远无法达到这样的境界:强大的新功能自然而然地从旧功能中衍生出来。
Other projects use an iterative process, but they fail to build up knowledge because they don’t abstract. Developers get the experts to describe a desired feature and then they go build it. They show the experts the result and ask what to do next. If the programmers practice refactoring, they can keep the software clean enough to continue extending it, but if programmers are not interested in the domain, they learn only what the application should do, not the principles behind it. Useful software can be built that way, but the project will never arrive at a point where powerful new features unfold as corollaries to older features.
优秀的程序员自然会开始抽象并开发能够完成更多工作的模型。但如果这种抽象仅仅发生在技术层面,而没有与领域专家合作,那么这些概念就显得幼稚。这种知识的浅薄会导致软件虽然能够完成基本功能,但却缺乏与领域专家思维方式的深度联系。
Good programmers will naturally start to abstract and develop a model that can do more work. But when this happens only in a technical setting, without collaboration with domain experts, the concepts are naive. That shallowness of knowledge produces software that does a basic job but lacks a deep connection to the domain expert’s way of thinking.
随着团队成员共同完善模型,彼此间的互动方式也随之改变。领域模型的不断完善迫使开发人员学习他们所服务业务的重要原则,而不是机械地编写功能。领域专家常常被迫将已有的知识提炼为本质,从而加深自身的理解,并最终领悟到软件项目所需的严谨概念。
The interaction between team members changes as all members crunch the model together. The constant refinement of the domain model forces the developers to learn the important principles of the business they are assisting, rather than to produce functions mechanically. The domain experts often refine their own understanding by being forced to distill what they know to essentials, and they come to understand the conceptual rigor that software projects require.
这一切都使团队成员成为更优秀的知识分析师。他们剔除无关信息,并将模型重塑成更有用的形式。由于分析师和程序员的参与,模型结构清晰、抽象化程度高,从而能够为实施提供助力。由于领域专家的参与,模型体现了对业务的深刻理解。这些抽象概念正是真正的业务原则。
All this makes the team members more competent knowledge crunchers. They winnow out the extraneous. They recast the model into an ever more useful form. Because analysts and programmers are feeding into it, it is cleanly organized and abstracted, so it can provide leverage for the implementation. Because the domain experts are feeding into it, the model reflects deep knowledge of the business. The abstractions are true business principles.
随着模型的不断完善,它成为组织项目中持续流动信息的工具。该模型聚焦于需求分析,并与编程和设计紧密结合。在一个良性循环中,它加深了团队成员对领域的理解,使他们能够更清晰地看待问题,并最终促使模型的进一步完善。这些模型永远不会完美,它们会不断发展演进。它们必须实用有效,能够帮助理解领域概念。它们必须足够严谨,才能使应用程序易于实现和理解。
As the model improves, it becomes a tool for organizing the information that continues to flow through the project. The model focuses requirements analysis. It intimately interacts with programming and design. And in a virtuous cycle, it deepens team members’ insight into the domain, letting them see more clearly and leading to further refinement of the model. These models are never perfect; they evolve. They must be practical and useful in making sense of the domain. They must be rigorous enough to make the application simple to implement and understand.
当我们着手编写软件时,我们永远无法掌握足够的信息。项目相关的知识支离破碎,散落在许多人和文档中,并且与其他信息混杂在一起,以至于我们甚至不知道自己真正需要哪些知识。那些看似技术难度较低的领域也可能具有欺骗性:我们往往意识不到自己究竟有多少知识盲区。这种无知会导致我们做出错误的假设。
When we set out to write software, we never know enough. Knowledge on the project is fragmented, scattered among many people and documents, and it’s mixed with other information so that we don’t even know which bits of knowledge we really need. Domains that seem less technically daunting can be deceiving: we don’t realize how much we don’t know. This ignorance leads us to make false assumptions.
与此同时,所有项目都会造成知识流失。学到东西的人会离开。重组会使团队分散,知识再次变得零散。关键子系统被外包,代码交付了,但知识却没有交付。而且,按照典型的设计方法,代码和文档也无法有效共享。将这种来之不易的知识以可用的形式表达出来,这样,当口头传统因任何原因中断时,这些知识就会丢失。
Meanwhile, all projects leak knowledge. People who have learned something move on. Reorganization scatters the team, and the knowledge is fragmented again. Crucial subsystems are outsourced in such a way that code is delivered but knowledge isn’t. And with typical design approaches, the code and documents don’t express this hard-earned knowledge in a usable form, so when the oral tradition is interrupted for any reason, the knowledge is lost.
高效团队会自觉地提升知识水平,践行持续学习(Kerievsky 2003)。对于开发人员而言,这意味着提升技术知识以及通用的领域建模技能(例如本书中介绍的技能)。但这同时也包括深入学习他们所从事的特定领域。
Highly productive teams grow their knowledge consciously, practicing continuous learning (Kerievsky 2003). For developers, this means improving technical knowledge, along with general domain-modeling skills (such as those in this book). But it also includes serious learning about the specific domain they are working in.
这些自学成才的团队成员构成了一个稳定的核心团队,专注于涉及最关键领域的开发任务。(更多内容请参见第15章。)核心团队积累的知识使他们能够更高效地处理知识。
These self-educated team members form a stable core of people to focus on the development tasks that involve the most critical areas. (For more on this, see Chapter 15.) The accumulated knowledge in the minds of this core team makes them more effective knowledge crunchers.
此时,停下来问问自己:你对PCB设计流程有什么新的了解吗?虽然这个例子只是对该领域进行了浅显的探讨,但讨论领域模型时,总应该有所收获。我学到了很多。我并没有学会如何成为一名PCB工程师,那并非我的目标。我学会了如何与PCB专家交流,理解与应用相关的核心概念,以及对我们构建的产品进行合理的检查。
At this point, stop and ask yourself a question. Did you learn something about the PCB design process? Although this example has been a superficial treatment of that domain, there should be some learning when a domain model is discussed. I learned an enormous amount. I did not learn how to be a PCB engineer. That was not the goal. I learned to talk to PCB experts, understand the major concepts relevant to the application, and sanity-check what we were building.
事实上,我们的团队最终发现探针模拟的开发优先级很低,最终该功能被完全取消了。随之被取消的还有模型中用于理解信号在组件间传输以及计算跃点数的部分。应用程序的核心功能原来在于其他方面,因此模型也进行了调整,将这些方面置于中心位置。领域专家们积累了更多经验,并明确了应用程序的目标。(第15章将深入探讨这些问题。)
In fact, our team eventually discovered that the probe simulation was a low priority for development, and the feature was eventually dropped altogether. With it went the parts of the model that captured understanding of pushing signals through components and counting hops. The core of the application turned out to lie elsewhere, and the model changed to bring those aspects onto center stage. The domain experts had learned more and had clarified the goal of the application. (Chapter 15 discusses these issues in depth.)
即便如此,早期工作仍然至关重要。关键模型要素得以保留,但更重要的是,这项工作启动了知识整合的过程,使后续所有工作都卓有成效:团队成员、开发人员和领域专家都从中获得了知识;一种共享语言初具雏形;并通过实施完善了反馈循环。探索之旅总要有个开始。
Even so, the early work was essential. Key model elements were retained, but more important, that work set in motion the process of knowledge crunching that made all subsequent work effective: the knowledge gained by team members, developers, and domain experts alike; the beginnings of a shared language; and the closing of a feedback loop through implementation. A voyage of discovery has to start somewhere.
像PCB示例这样的模型所捕捉到的知识远不止“找出名词”那么简单。业务活动和规则与所涉及的实体一样,都是领域的核心;任何领域都包含各种概念类别。知识的积累会产生反映这类洞察的模型。在模型变更的同时,开发人员也会重构实现以表达模型,从而使应用程序能够利用这些知识。
The kind of knowledge captured in a model such as the PCB example goes beyond “find the nouns.” Business activities and rules are as central to a domain as are the entities involved; any domain will have various categories of concepts. Knowledge crunching yields models that reflect this kind of insight. In parallel with model changes, developers refactor the implementation to express the model, giving the application use of that knowledge.
正是由于超越了实体和价值的范畴,知识挖掘才会变得异常艰辛,因为业务规则之间可能存在实际的不一致性。领域专家通常意识不到自身思维过程的复杂性,因为他们在工作中需要梳理所有这些规则,调和矛盾,并运用常识填补空白。软件无法做到这一点。只有通过与软件专家紧密合作进行知识挖掘,才能澄清、完善、协调规则,或将其排除在软件范围之外。
It is with this move beyond entities and values that knowledge crunching can get intense, because there may be actual inconsistency among business rules. Domain experts are usually not aware of how complex their mental processes are as, in the course of their work, they navigate all these rules, reconcile contradictions, and fill in gaps with common sense. Software can’t do this. It is through knowledge crunching in close collaboration with software experts that the rules are clarified, fleshed out, reconciled, or placed out of scope.
让我们从一个非常简单的领域模型开始,它可以作为船舶航程货物预订应用程序的基础。
Let’s start with a very simple domain model that could be the basis of an application for booking cargos onto a voyage of a ship.
图 1.8
Figure 1.8
我们可以说,订舱应用程序的职责是将每件货物与一个航次关联起来,并记录和跟踪这种关联。到目前为止一切顺利。应用程序代码中可能存在类似这样的方法:
We can state that the booking application’s responsibility is to associate each Cargo with a Voyage, recording and tracking that relationship. So far so good. Somewhere in the application code there could be a method like this:
public int makeBooking(Cargo cargo, Voyage voyage) {
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
由于总会有临时取消的情况发生,航运业的惯例是接受比特定船舶单次航程可承载量更多的货物。这被称为“超额预订”。有时采用简单的运力百分比,例如预订110%的运力。而在其他情况下,则会应用复杂的规则,以偏袒主要客户或特定类型的货物。
Because there are always last-minute cancellations, standard practice in the shipping industry is to accept more cargo than a particular vessel can carry on a voyage. This is called “overbooking.” Sometimes a simple percentage of capacity is used, such as booking 110 percent of capacity. In other cases complex rules are applied, favoring major customers or certain kinds of cargo.
这是航运领域的一项基本策略,航运业的任何商人都会了解,但软件团队中的所有技术人员可能并不理解。
This is a basic strategy in the shipping domain that would be known to any businessperson in the shipping industry, but it might not be understood by all technical people on a software team.
需求文档中包含这样一行:
The requirements document contains this line:
允许超额预订10%。
Allow 10% overbooking.
现在的类图和代码如下所示:
The class diagram and code now look like this:
图 1.9
Figure 1.9
public int makeBooking(Cargo cargo, Voyage voyage) {
double maxBooking = voyage.capacity() * 1.1;
if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
return –1;
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
现在,一条重要的业务规则被隐藏在应用程序方法的守卫子句中。稍后,在第四章中,我们将探讨分层架构原则,该原则将指导我们如何将超额预订规则移至领域对象中。但现在,让我们集中精力思考如何让项目中的每个人都能更清晰地理解和使用这条规则。这将引导我们找到类似的解决方案。
Now an important business rule is hidden as a guard clause in an application method. Later, in Chapter 4, we’ll look at the principle of LAYERED ARCHITECTURE, which would guide us to move the over-booking rule into a domain object, but for now let’s concentrate on how we could make this knowledge more explicit and accessible to everyone on the project. This will bring us to a similar solution.
1.按照目前的写法,即使有开发人员的指导,任何业务专家也不太可能能够阅读这段代码来验证该规则。
1. As written, it is unlikely that any business expert could read this code to verify the rule, even with the guidance of a developer.
2.对于一个非业务技术人员来说,将需求文本与代码联系起来是很困难的。
2. It would be difficult for a technical, non-businessperson to connect the requirement text with the code.
如果规则更复杂,那么后果就更严重了。
If the rule were more complex, that much more would be at stake.
我们可以改进设计,以便更好地利用这些信息。超额预订规则是一种策略。策略是另一种设计模式的名称,该模式被称为战略模式(Gamma 等人,1995)。它通常是有动机的。由于需要替换不同的规则,而据我们所知,这里并不需要这样做。但我们试图表达的概念确实符合策略的含义,这在领域驱动设计中是一个同样重要的动机。(参见第 12 章,“将设计模式与模型关联起来”。)
We can change the design to better capture this knowledge. The overbooking rule is a policy. Policy is another name for the design pattern known as STRATEGY (Gamma et al. 1995). It is usually motivated by the need to substitute different rules, which is not needed here, as far as we know. But the concept we are trying to capture does fit the meaning of a policy, which is an equally important motivation in domain-driven design. (See Chapter 12, “Relating Design Patterns to the Model.”)
图 1.10
Figure 1.10
现在的代码是:
The code is now:
public int makeBooking(Cargo cargo, Voyage voyage) {
if (!overbookingPolicy.isAllowed(cargo, voyage)) return –1;
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
新的超额预订策略类包含以下方法:
The new Overbooking Policy class contains this method:
public boolean isAllowed(Cargo cargo, Voyage voyage) {
return (cargo.size() + voyage.bookedCargoSize()) <=
(voyage.capacity() * 1.1);
}
所有人都会明白,超额预订是一项独立的政策,该规则的执行是明确且单独的。
It will be clear to all that overbooking is a distinct policy, and the implementation of that rule is explicit and separate.
当然,我并不是建议将如此精细的设计应用于领域的每一个细节。 第15章“提炼”深入探讨了如何专注于重要事项,并最小化或分离其他所有内容。本示例旨在说明领域模型及其相应的设计可用于保障和共享知识。更明确的设计具有以下优势:
Now, I am not recommending that such an elaborate design be applied to every detail of the domain. Chapter 15, “Distillation,” goes into depth on how to focus on the important and minimize or separate everything else. This example is meant to show that a domain model and corresponding design can be used to secure and share knowledge. The more explicit design has these advantages:
1.为了将设计推进到这个阶段,程序员和所有其他相关人员都必须了解其本质。将超额预订视为一项独立而重要的业务规则,而不仅仅是一项晦涩的计算。
1. In order to bring the design to this stage, the programmers and everyone else involved will have come to understand the nature of overbooking as a distinct and important business rule, not just an obscure calculation.
2.程序员可以向业务专家展示技术成果,甚至是代码,这些成果(在指导下)应该能够被领域专家理解,从而形成反馈闭环。
2. Programmers can show business experts technical artifacts, even code, that should be intelligible to domain experts (with guidance), thereby closing the feedback loop.
有用的模型很少显而易见。随着我们对领域和应用需求的理解不断加深,我们通常会舍弃那些起初看似重要的表面模型元素,或者转变看待它们的视角。一些微妙的抽象概念会逐渐浮现,这些概念起初我们并未想到,但却直击问题的核心。
Useful models seldom lie on the surface. As we come to understand the domain and the needs of the application, we usually discard superficial model elements that seemed important in the beginning, or we shift their perspective. Subtle abstractions emerge that would not have occurred to us at the outset but that pierce to the heart of the matter.
前面的例子大致基于我将在本书中多次引用的一个项目:集装箱运输系统。本书中的例子力求让非航运专业人士也能理解。但在实际项目中,团队成员需要不断学习,因此实用且清晰的模型往往需要在领域知识和建模技术方面都更加精深。
The preceding example is loosely based on one of the projects that I’ll be drawing on for several examples throughout the book: a container shipping system. The examples in this book will be kept accessible to non-shipping experts. But on a real project, where continuous learning prepares the team members, models of utility and clarity often call for sophistication both in the domain and in modeling technique.
在那个项目中,由于货物运输始于货物预订,我们开发了一个模型,用于描述货物、运输路线等等。这些都必不可少且非常有用,但领域专家们却并不满意。他们看待业务的方式,我们却忽略了。
On that project, because a shipment begins with the act of booking cargo, we developed a model that allowed us to describe the cargo, its itinerary, and so on. This was all necessary and useful, yet the domain experts felt dissatisfied. There was a way they looked at their business that we were missing.
经过数月的知识搜集,我们最终意识到,货物处理、装卸以及运输等工作,大多由分包商或公司内部人员完成。在我们航运专家看来,各方之间存在着一系列的责任转移。这种法律和实际责任的转移遵循着一套流程:从托运人到当地承运人,再从一家承运人到另一家承运人,最终到达收货人。很多时候,货物会滞留在仓库中,等待重要的处理步骤完成。有时,货物会经历一些与航运公司业务决策无关的复杂物理步骤。行程安排的后勤保障工作中,最突出的是提单等法律文件以及付款流程。
Eventually, after months of knowledge crunching, we realized that the handling of cargo, the physical loading and unloading, the movements from place to place, was largely carried out by subcontractors or by operational people in the company. In the view of our shipping experts, there was a series of transfers of responsibility between parties. A process governed that transfer of legal and practical responsibility, from the shipper to some local carrier, from one carrier to another, and finally to the consignee. Often, the cargo would sit in a warehouse while important steps were being taken. At other times, the cargo would move through complex physical steps that were not relevant to the shipping company’s business decisions. Rather than the logistics of the itinerary, what came to the fore were legal documents such as the bill of lading, and processes leading to the release of payments.
对航运业务的这种更深入的理解并没有导致行程对象的移除,但模型发生了深刻的改变。我们对航运的理解从单纯地将集装箱从一个地方运送到另一个地方,转变为将货物责任从一个实体转移到另一个实体。处理这些责任转移的功能不再生硬地附加在装载操作上,而是由一个基于对这些操作和责任之间重要关系的理解而构建的模型来支撑。
This deeper view of the shipping business did not lead to the removal of the Itinerary object, but the model changed profoundly. Our view of shipping changed from moving containers from place to place, to transferring responsibility for cargo from entity to entity. Features for handling these transfers of responsibility were no longer awkwardly attached to loading operations, but were supported by a model that came out of an understanding of the significant relationship between those operations and those responsibilities.
知识的积累是一场探索,你永远无法预知最终会走到哪里。
Knowledge crunching is an exploration, and you can’t know where you will end up.
领域模型可以成为软件项目通用语言的核心。该模型是项目团队成员头脑中构建的一系列概念,其术语和关系反映了对领域的深刻理解。这些术语和相互关系构成了一种语义,这种语义既针对特定领域进行了定制,又足够精确,能够满足技术开发的需求。这就像一条关键的纽带,将模型融入开发活动,并将其与代码紧密结合。
A domain model can be the core of a common language for a software project. The model is a set of concepts built up in the heads of people on the project, with terms and relationships that reflect domain insight. These terms and interrelationships provide the semantics of a language that is tailored to the domain while being precise enough for technical development. This is a crucial cord that weaves the model into development activity and binds it with the code.
这种基于模型的沟通方式并不局限于统一建模语言(UML)中的图表。为了最有效地利用模型,它需要渗透到每一种沟通媒介中。它提高了书面文档的效用,也提升了敏捷流程中反复强调的非正式图表和日常对话的效用。它通过代码本身以及代码测试来改进沟通。
This model-based communication is not limited to diagrams in Unified Modeling Language (UML). To make most effective use of a model, it needs to pervade every medium of communication. It increases the utility of written text documents, as well as the informal diagrams and casual conversation reemphasized in Agile processes. It improves communication through the code itself and through the tests for that code.
项目中语言的运用虽然微妙,但却至关重要……
The use of language on a project is subtle but all-important. . . .
首先你写一个句子,
然后把它切碎;
然后把这些碎片混合在一起,再把它们整理好,
就像它们偶然落下一样:
短语的顺序
完全没有影响。
For first you write a sentence,
And then you chop it small;
Then mix the bits, and sort them out
Just as they chance to fall:
The order of the phrases makes
No difference at all.
——刘易斯·卡罗尔,《Poeta Fit,Non Nascitur》
—Lewis Carroll, “Poeta Fit, Non Nascitur”
要创造一个灵活、知识丰富的设计,需要一种多功能、共享的团队语言,以及对语言的活跃实验,而这在软件项目中很少发生。
To create a supple, knowledge-rich design calls for a versatile, shared team language, and a lively experimentation with language that seldom happens on software projects.
领域专家对软件开发的专业术语了解有限,但他们会使用自己领域的术语——而且可能使用方式各不相同。另一方面,开发人员可能会用描述性的、功能性的术语来理解和讨论系统,而这些术语可能缺乏专家语言所蕴含的意义。或者,开发人员可能会创建一些抽象概念来支持他们的设计,但这些抽象概念可能不被领域专家理解。负责解决问题不同部分的开发人员会形成自己的设计理念和描述领域的方法。
Domain experts have limited understanding of the technical jargon of software development, but they use the jargon of their field—probably in various flavors. Developers, on the other hand, may understand and discuss the system in descriptive, functional terms, devoid of the meaning carried by the experts’ language. Or developers may create abstractions that support their design but are not understood by the domain experts. Developers working on different parts of the problem work out their own design concepts and ways of describing the domain.
由于语言障碍,领域专家只能含糊地描述他们的需求。开发人员努力理解这个对他们来说全新的领域,也只能模糊地理解。团队中虽然有少数成员能够掌握双语,但他们却成了信息流通的瓶颈,而且他们的翻译也不准确。
Across this linguistic divide, the domain experts vaguely describe what they want. Developers, struggling to understand a domain new to them, vaguely understand. A few members of the team manage to become bilingual, but they become bottlenecks of information flow, and their translations are inexact.
在一个缺乏通用语言的项目中,开发人员必须为领域专家进行翻译。领域专家之间也需要进行翻译,开发人员之间甚至还要互相翻译。这种翻译会混淆模型概念,导致破坏性的代码重构。沟通的间接性掩盖了分歧的形成——不同的团队成员使用不同的术语,却浑然不觉。这会导致软件不可靠且无法协同工作(参见第14章)。翻译工作阻碍了知识和思想的交流互动,而这些互动正是深入理解模型的关键。
On a project without a common language, developers have to translate for domain experts. Domain experts translate between developers and still other domain experts. Developers even translate for each other. Translation muddles model concepts, which leads to destructive refactoring of code. The indirectness of communication conceals the formation of schisms—different team members use terms differently but don’t realize it. This leads to unreliable software that doesn’t fit together (see Chapter 14). The effort of translation prevents the interplay of knowledge and ideas that lead to deep model insights.
当项目语言出现分裂时,就会面临严重的问题。领域专家使用他们的专业术语,而技术团队成员则使用他们自己的语言,这种语言更适合从设计的角度讨论领域问题。
A project faces serious problems when its language is fractured. Domain experts use their jargon while technical team members have their own language tuned for discussing the domain in terms of design.
日常讨论中使用的术语与代码(最终是软件项目最重要的成果)中使用的术语脱节。即使是同一个人,口头和书面表达也各不相同,因此,该领域最精辟的表达往往以一种转瞬即逝的形式出现,既没有被记录在代码中,也没有被记录在书面文件中。
The terminology of day-to-day discussions is disconnected from the terminology embedded in the code (ultimately the most important product of a software project). And even the same person uses different language in speech and in writing, so that the most incisive expressions of the domain often emerge in a transient form that is never captured in the code or even in writing.
翻译会削弱沟通,使知识的理解变得苍白无力。
Translation blunts communication and makes knowledge crunching anemic.
然而,这些方言都不能成为通用语言,因为它们都无法满足所有需求。
Yet none of these dialects can be a common language because none serves all needs.
所有翻译带来的额外成本,加上误解的风险,实在太高了。一个项目需要一种比最低公分母更稳健的通用语言。通过团队的共同努力,领域模型可以为这种通用语言提供骨架,同时将团队沟通与软件实现连接起来。这种语言可以贯穿团队的日常工作。
The overhead cost of all the translation, plus the risk of misunderstanding, is just too high. A project needs a common language that is more robust than the lowest common denominator. With a conscious effort by the team, the domain model can provide the backbone for that common language, while connecting team communication to the software implementation. That language can be ubiquitous in the team’s work.
这种通用语言的词汇表包含了类名和重要操作的名称。该语言还包含用于讨论模型中已明确定义的规则的术语。此外,它还补充了模型所采用的高级组织原则中的术语(例如上下文映射和大规模结构,这些内容将在第14章和第16章中讨论)。最后,该语言还包含了团队通常应用于领域模型的模式名称。
The vocabulary of that UBIQUITOUS LANGUAGE includes the names of classes and prominent operations. The LANGUAGE includes terms to discuss rules that have been made explicit in the model. It is supplemented with terms from high-level organizing principles imposed on the model (such as CONTEXT MAPS and large-scale structures, which will be discussed in Chapters 14 and 16). Finally, this language is enriched with the names of patterns the team commonly applies to the domain model.
模型关系成为所有语言都具备的组合规则。词语和短语的含义反映了模型的语义。
The model relationships become the combinatory rules all languages have. The meanings of words and phrases echo the semantics of the model.
开发人员应该使用基于模型的语言来描述系统中的各种工件,以及任务和功能。同样的模型也应该为开发人员和领域专家之间的沟通,以及领域专家之间就需求、开发计划和特性进行交流提供语言。这种语言的使用越广泛,理解就越顺畅。
The model-based language should be used among developers to describe not only artifacts in the system, but tasks and functionality. This same model should supply the language for the developers and domain experts to communicate with each other, and for the domain experts to communicate among themselves about requirements, development planning, and features. The more pervasively the language is used, the more smoothly understanding will flow.
至少,这是我们需要努力的方向。但最初,这个模型可能不足以胜任这些角色。它可能缺乏该领域专业术语所蕴含的丰富语义。然而,这些术语不能直接使用,因为它们本身就包含歧义和矛盾。它可能缺少开发者在代码中创建的那些更微妙、更主动的功能,要么是因为开发者没有将这些功能视为模型的一部分,要么是因为编码风格是过程式的,只是隐式地承载了这些领域概念。
At least, this is where we need to go. But initially the model may simply not be good enough to fill these roles. It may lack the semantic richness of the specialized jargons of the field. But those jargons can’t be used unadulterated because they contain ambiguities and contradictions. It may lack the more subtle and active features the developers have created in the code, either because they do not think of those as part of a model, or because the coding style is procedural and only implicitly carries those concepts of the domain.
尽管这个过程看似循环往复,但能够产生更实用模型的知识积累过程取决于团队对基于模型的语言的坚持。持续使用通用语言将迫使模型的缺陷暴露出来。团队会进行试验,寻找替代生硬术语或组合的方案。随着语言中出现不足,新的词汇也会被纳入讨论。这些语言上的变化将被视为领域模型的变化,并促使团队更新类图、重命名代码中的类和方法,甚至在术语含义发生变化时改变其行为。
But although the sequence seems circular, the knowledge crunching process that can produce a more useful kind of model depends on the team’s commitment to model-based language. Persistent use of the UBIQUITOUS LANGUAGE will force the model’s weaknesses into the open. The team will experiment and find alternatives to awkward terms or combinations. As gaps are found in the language, new words will enter the discussion. These changes to the language will be recognized as changes in the domain model and will lead the team to update class diagrams and rename classes and methods in the code, or even change behavior, when the meaning of a term changes.
开发人员致力于在实施过程中使用这种语言,他们会指出其中的不精确之处或矛盾之处,并与领域专家一起寻找可行的替代方案。
Committed to using this language in the context of implementation, the developers will point out imprecision or contradictions, engaging the domain experts in discovering workable alternatives.
当然,领域专家会使用超出通用语言范围的语言进行解释和提供更广泛的背景信息。但在模型所涵盖的范围内,他们应该使用通用语言,并在发现语言表达生硬、不完整或错误时提出质疑。通过广泛使用基于模型的语言,并不断改进直至流畅自然,我们最终会构建出一个完整且易于理解的模型,该模型由简单的元素组合而成,能够表达复杂的概念。
Of course, domain experts will speak outside the scope of the UBIQUITOUS LANGUAGE, to explain and give broader context. But within the scope the model addresses, they should use LANGUAGE and raise concerns when they find it awkward or incomplete—or wrong. By using the model-based language pervasively and not being satisfied until it flows, we approach a model that is complete and comprehensible, made up of simple elements that combine to express complex ideas.
所以:
Therefore:
将该模型作为语言的骨架。要求团队在团队内部的所有沟通和代码编写中始终坚持使用这种语言。在图表、文字,尤其是口头交流中,都要使用相同的语言。
Use the model as the backbone of a language. Commit the team to exercising that language relentlessly in all communication within the team and in the code. Use the same language in diagrams, writing, and especially speech.
通过尝试不同的表达方式来解决难题,这些表达方式反映了不同的模型。然后重构代码,重命名类、方法和模块,使其符合新的规范。模型。解决对话中因用语引起的歧义,就像我们最终就普通词语的含义达成一致一样。
Iron out difficulties by experimenting with alternative expressions, which reflect alternative models. Then refactor the code, renaming classes, methods, and modules to conform to the new model. Resolve confusion over terms in conversation, in just the way we come to agree on the meaning of ordinary words.
要认识到,普遍语言的改变就是模型的改变。
Recognize that a change in the UBIQUITOUS LANGUAGE is a change to the model.
领域专家应该反对那些表达领域理解生硬或不充分的术语或结构;开发人员应该注意那些会阻碍设计的歧义或不一致之处。
Domain experts should object to terms or structures that are awkward or inadequate to convey domain understanding; developers should watch for ambiguity or inconsistency that will trip up design.
借助普适语言,模型不再仅仅是设计产物,而是融入到开发人员和领域专家共同开展的所有工作中。这种语言以动态形式承载知识,用这种语言进行的讨论能够生动地阐释图表和代码背后的含义。
With a UBIQUITOUS LANGUAGE, the model is not just a design artifact. It becomes integral to everything the developers and domain experts do together. The LANGUAGE carries knowledge in a dynamic form. Discussion in the LANGUAGE brings to life the meaning behind the diagrams and code.
本文对普适语言的讨论假设只有一个模型在运行。第14章“维护模型完整性”探讨了不同模型(和语言)的共存问题,以及如何防止模型分裂。
This discussion of UBIQUITOUS LANGUAGE assumes that there is just one model in play. Chapter 14, “Maintaining Model Integrity,” deals with the coexistence of different models (and LANGUAGES) and how to keep a model from splintering.
普遍语言是设计中那些不出现在代码中的方面的主要载体——组织整个系统的大规模结构(见第 16 章)、定义不同系统和模型之间关系的有界上下文(见第 14 章)以及应用于模型和设计的其他模式。
The UBIQUITOUS LANGUAGE is the primary carrier of the aspects of design that don’t appear in code—large-scale structures that organize the whole system (see Chapter 16), BOUNDED CONTEXTS that define the relationships of different systems and models (see Chapter 14), and other patterns applied to the model and design.
以下两段对话存在细微但重要的差异。在每种情况下,请注意说话者谈论软件对业务的意义与谈论其技术运作方式的程度。用户和开发者是否使用相同的语言?这种语言是否足够丰富,能够清晰地阐述应用程序必须实现的功能?
The following two dialogs have subtle, but important, differences. In each scenario, watch for how much the speakers talk about what the software means to the business versus how it works technically. Are the user and developer speaking the same language? Is that language rich enough to carry the discussion of what the application must do?
图 2.1
Figure 2.1
用户: 所以当我们更改清关点时,我们需要重新制定整个路线计划。
User: So when we change the customs clearance point, we need to redo the whole routing plan.
开发人员:好的。我们会删除货运表中所有具有该货物 ID 的行,然后将始发地、目的地和新的清关点传递给路由服务,它会重新填充该表。我们需要在货物字段中添加一个布尔值,以便知道货运表中已有数据。
Developer: Right. We’ll delete all the rows in the shipment table with that cargo id, then we’ll pass the origin, destination, and the new customs clearance point into the Routing Service, and it will re-populate the table. We’ll have to have a Boolean in the Cargo so we’ll know there is data in the shipment table.
用户:删除这些行?好吧,随便吧。反正,如果我们之前根本没有海关清关点,我们也得这么做。
User: Delete the rows? OK, whatever. Anyway, if we didn’t have a customs clearance point at all before, we’ll have to do the same thing.
开发者:当然,无论何时您更改始发地、目的地或清关点(或首次输入),我们都会检查是否有货运数据,然后将其删除,并让路由服务重新生成。
Developer: Sure, anytime you change the origin, destination, or customs clearance point (or enter one for the first time), we’ll check to see if we have shipment data and then we’ll delete it and then let the Routing Service regenerate it.
用户:当然,如果旧的清关文件恰好是正确的,我们肯定不想那样做。
User: Of course, if the old customs clearance just happened to be the right one, we wouldn’t want to do that.
开发人员:哦,没问题。让路由服务每次都重新执行加载和卸载操作会更简单。
Developer: Oh, no problem. It’s easier to just make the Routing Service redo the loads and unloads every time.
用户:是的,但是为新的行程制定所有配套计划会给我们带来额外的工作量,所以除非情况有变,否则我们不想更改路线。
User: Yes, but it’s extra work for us to make all the supporting plans for a new itinerary, so we don’t want to reroute unless the change necessitates it.
开发人员: 唉。好吧,如果您是第一次输入某个清关点信息,我们需要查询数据库表,找到之前生成的清关点信息,然后与新的清关点信息进行比较。这样我们才能知道是否需要重新录入。
Developer: Ugh. Well, then, if you are entering a customs clearance point for the first time, we’ll have to query the table to find the old derived customs clearance point, and then compare it to the new one. Then we’ll know if we need to redo it.
用户:无论出发地还是目的地,您都不必担心这个问题,因为行程总会随之改变。
User: You won’t have to worry about this on origin or destination, since the itinerary would always change then.
开发商:很好。我们不会。
Developer: Good. We won’t.
图 2.2
Figure 2.2
用户:所以当我们更改清关点时,我们需要重新制定整个路线计划。
User: So when we change the customs clearance point, we need to redo the whole routing plan.
开发人员:好的。当您更改路线规范中的任何属性时,我们将删除旧的行程,并要求路线服务根据新的路线规范生成新的行程。
Developer: Right. When you change any of the attributes in the Route Specification, we’ll delete the old Itinerary and ask the Routing Service to generate a new one based on the new Route Specification.
用户:如果我们之前没有指定过海关清关点,那么我们现在也需要指定。
User: If we hadn’t specified a customs clearance point at all before, we’ll have to do that at the same time.
开发者:当然,只要你更改了路线规范中的任何内容,我们就会重新生成行程。这包括首次输入某些内容。
Developer: Sure, anytime you change anything in the Route Spec, we’ll regenerate the Itinerary. That includes entering something for the first time.
用户:当然,如果旧的清关文件恰好是正确的,我们肯定不想那样做。
User: Of course, if the old customs clearance just happened to be the right one, we wouldn’t want to do that.
开发者:哦,没问题。让路线规划服务每次都重新生成行程更容易些。
Developer: Oh, no problem. It’s easier to just make the Routing Service redo the Itinerary every time.
用户:是的,但是为新的行程制定所有配套计划会给我们带来额外的工作量,所以除非情况需要,否则我们不想更改路线。
User: Yes, but it’s extra work for us to make all the supporting plans for a new Itinerary, so we don’t want to reroute unless the change necessitates it.
开发人员:哦。那我们需要在路线规范中添加一些功能。这样,每当您更改规范中的任何内容时,我们都会检查行程是否仍然符合规范。如果不再符合,我们将让路线服务重新生成行程。
Developer: Oh. Then we’ll have to add some functionality to the Route Specification. Then, whenever you change anything in the Spec, we’ll see if the Itinerary still satisfies the Specification. If it doesn’t, we’ll have the Routing Service regenerate the Itinerary.
用户:您不必担心出发地或目的地的问题,因为行程总会随之改变。
User: You won’t have to worry about this on origin or destination, since the Itinerary would always change then.
开发人员:好的,但对我们来说,每次都进行比较会更简单。只有当路线规范不再满足时,才会生成行程单。
Developer: Fine, but it will be simpler for us to just do the comparison every time. The Itinerary will only be generated when the Route Specification is no longer satisfied.
第二个对话更能体现领域专家的意图。用户在两个对话中都使用了“行程安排”一词,但在第二个对话中,它成为了一个双方可以具体、精确讨论的对象。他们明确地讨论了“路线规范”,而不是每次都用属性和步骤来描述它。
The second dialog conveys more of the intent of the domain expert. The user employed the word “itinerary” in both dialogs, but in the second it was an object the two could discuss precisely, concretely. They discussed the “route specification” explicitly, instead of describing it each time in terms of attributes and procedures.
这两个对话经过精心设计,力求彼此呼应。实际上,第一个对话会更加冗长,充斥着对应用程序功能的解释和沟通误解。而第二个对话采用了基于领域模型的术语,因此更加简洁明了。
These two dialogs were deliberately constructed to closely parallel each other. Realistically, the first would have been more verbose, bloated with explanations of application features and miscommunications. The domain-model-based terminology of the second design makes the second dialog more concise.
语言与其他交流方式的分离是一种特别巨大的损失,因为我们人类天生擅长口语。不幸的是,人们说话时,通常不会使用领域模型的语言。
The detachment of speech from other forms of communication is a particularly great loss because we humans have a genius for spoken language. Unfortunately, when people speak, they usually don’t use the language of the domain model.
这句话你可能一开始并不认同,而且确实存在例外情况。但下次你参加需求或设计讨论时,请认真倾听。你会听到对功能的描述……你会听到各种商业术语,或者用通俗易懂的方式解释这些术语。你会听到关于技术工件和具体功能的讨论。当然,你也会听到领域模型中的术语;商业术语中那些显而易见的名词通常会被编码为对象,因此这些术语往往会被提及。但是,你是否听到过任何可以用当前领域模型中的关系和交互来描述的短语呢?
That statement may not ring true for you initially, and indeed there are exceptions. But the next time you attend a requirements or design discussion, really listen. You’ll hear descriptions of features in business jargon or layman’s versions of the jargon. You’ll hear talk about technical artifacts and concrete functionality. Sure, you’ll hear terms from the domain model; obvious nouns in the common language from the business jargon will typically be coded as objects, and so those terms will tend to be mentioned. But do you hear phrases that could even remotely be described in terms of relationships and interactions in your current domain model?
改进模型的最佳方法之一是通过语音进行探索,大声尝试各种可能的模型变体结构。这样很容易听出模型的不足之处。
One of the best ways to refine a model is to explore with speech, trying out loud various constructs from possible model variations. Rough edges are easy to hear.
“如果我们向路线规划服务提供始发站、目的地和到达时间,它就可以查找货物需要停靠的站点,然后……把它们录入数据库。”(含糊且技术性强)
“If we give the Routing Service an origin, destination, and arrival time, it can look up the stops the cargo will have to make and, well . . . stick them in the database.” (vague and technical)
“出发地、目的地等等……所有这些信息都会输入到路线规划服务中,我们会收到一份包含所有必要信息的行程单。”(更完整,但略显冗长)
“The origin, destination, and so on . . . it all feeds into the Routing Service, and we get back an Itinerary that has everything we need in it.” (more complete, but verbose)
“路线规划服务会找到满足路线规范的行程。”(简明版)
“A Routing Service finds an Itinerary that satisfies a Route Specification.” (concise)
运用语言能力进行建模至关重要,正如运用视觉/空间推理能力绘制图表同样重要一样。正如我们运用分析能力进行系统分析和设计,以及代码中那种神秘的“感觉”一样。这些思维方式相辅相成,我们需要将它们全部结合起来才能找到有用的模型和设计。然而,在所有这些方法中,语言实验往往最容易被忽视。(本书第三部分将深入探讨这一发现过程,并通过几个对话展示这种相互作用。)
It is vital that we play around with words and phrases, harnessing our linguistic abilities to the modeling effort, just as it is vital to engage our visual/spatial reasoning by sketching diagrams. Just as we employ our analytical abilities with methodical analysis and design, and that mysterious “feel” of the code. These ways of thinking complement each other, and it takes all of them to find useful models and designs. Of all of these, experimenting with language is most often overlooked. (Part III of this book will delve into this discovery process and show this interplay in several dialogs.)
事实上,我们的大脑似乎天生就擅长处理口语的复杂性(像我这样的外行可以读一读史蒂芬·平克 (Steven Pinker) 的《语言本能》(The Language Instinct ) [ Pinker 1994 ])。例如,当不同语言背景的人们为了商业目的聚集在一起时,如果他们没有共同语言,就会创造一种语言,称为皮钦语。皮钦语不如说话者的母语那样全面,但它适合当前的交流需求。人们在交谈中自然会发现彼此对词语含义和理解上的差异,并自然而然地解决这些差异。他们找出语言中的不足之处,并将其加以润色。
In fact, our brains seem to be somewhat specialized for dealing with complexity in spoken language (one good treatment for laymen, like myself, is The Language Instinct, by Steven Pinker [Pinker 1994]). For example, when people of different language backgrounds come together for commerce, if they don’t have a common language they invent one, called a pidgin. The pidgin is not as comprehensive as the speakers’ original languages, but it is suited to the task at hand. When people are talking, they naturally discover differences in interpretation and the meaning of their words, and they naturally resolve those differences. They find rough spots in the language and smooth them out.
大学时我上过一门强化西班牙语课。课堂规定是绝对不能说英语。一开始,这让我很沮丧,感觉非常不自然,而且需要很强的自律性。但最终,我和同学们都突破了瓶颈,达到了仅靠纸上练习永远无法达到的流利程度。
Once I took an intensive Spanish class in college. The rule in the classroom was that not a word of English could be spoken. At first, it was frustrating. It felt very unnatural, and required a lot of self-discipline. But eventually my classmates and I broke through to a level of fluency that we could never have reached through exercises on paper.
当我们在讨论中使用领域模型这种通用语言时——尤其是在开发人员和领域专家共同探讨场景和需求时——我们会越来越熟练地掌握这种语言,并互相传授其中的细微差别。我们自然而然地就能分享彼此的语言,而这在使用图表和文档时是无法实现的。
As we use the UBIQUITOUS LANGUAGE of the domain model in discussions—especially discussions in which developers and domain experts hash out scenarios and requirements—we become more fluent in the language and teach each other its nuances. We naturally come to share the language that we speak in a way that never happens with diagrams and documents.
在软件项目中实现通用语言并非易事,我们需要充分发挥自身天赋才能做到。正如人类的视觉和空间能力使我们能够通过图形概览快速传递和处理信息一样,我们也可以利用自身对语法正确、含义丰富的语言的天赋来推动模型开发。
Bringing about a UBIQUITOUS LANGUAGE on a software project is easier said than done, and we have to fully employ our natural talents to pull it off. Just as humans’ visual and spatial capabilities let us convey and process information rapidly in graphical overviews, we can exploit our innate talent for grammatical, meaningful language to drive model development.
因此,作为对普遍语言模式的补充:
Therefore, as an addendum to the UBIQUITOUS LANGUAGE pattern:
在讨论系统时,不妨先使用模型进行操作。大声描述各种场景,运用模型中的元素和交互方式,并根据模型允许的方式组合不同的概念。找到更简洁的表达方式,然后将这些新想法应用到图表和代码中。
Play with the model as you talk about the system. Describe scenarios out loud using the elements and interactions of the model, combining concepts in ways allowed by the model. Find easier ways to say what you need to say, and then take those new ideas back down to the diagrams and code.
技术人员常常觉得有必要“保护”业务专家免受领域模型的影响。他们说:
Technical people often feel the need to “shield” the business experts from the domain model. They say:
“对他们来说太抽象了。”
“Too abstract for them.”
“他们不理解物体。”
“They don’t understand objects.”
“我们必须用他们的术语来收集需求。”
“We have to collect requirements in their terminology.”
These are just a few of the reasons I’ve heard for having two languages on the team. Forget them.
当然,设计中有些技术细节可能与领域专家无关,但模型的核心内容最好能引起他们的兴趣。如果模型过于抽象,又如何确定这些抽象概念是否合理呢?你是否像他们一样深入地理解了该领域?有时,我们会从基层用户那里收集具体需求,并可能需要使用一些更具体的术语,但我们假定领域专家能够对其领域进行较为深入的思考。如果连资深领域专家都无法理解模型,那么这个模型肯定存在问题。
Of course there are technical components of the design that may not concern the domain experts, but the core of the model had better interest them. Too abstract? Then how do you know the abstractions are sound? Do you understand the domain as deeply as they do? Sometimes specific requirements are collected from lower-level users, and a subset of the more concrete terminology may be needed for them, but a domain expert is assumed to be capable of thinking somewhat deeply about his or her field. If sophisticated domain experts don’t understand the model, there is something wrong with the model.
在初期,当用户讨论系统未来尚未建模的功能时,他们没有现成的模型可供参考。但一旦他们开始与开发人员共同探讨这些新想法,便会开始摸索构建共享模型的过程。这个过程起初可能显得笨拙且不完整,但会逐步完善。随着新语言的演进,领域专家必须付出额外的努力来适应它,并对任何仍然重要的旧文档进行相应的调整。
Now at the beginning, when the users are discussing future capabilities of the system that haven’t been modeled yet, there is no model for them to use. But as soon as they begin to work through these new ideas with the developers, the process of groping toward a shared model begins. It may start out awkward and incomplete, but it will gradually get refined. As the new language evolves, the domain experts must make the extra effort to adopt it, and to retrofit any old documents that are still important.
当领域专家在与开发人员或彼此交流时使用这种语言,他们很快就会发现模型在某些方面无法满足他们的需求,或者在他们看来存在错误。领域专家(在开发人员的帮助下)还会发现,基于模型的语言的精确性在某些方面暴露了他们思维中的矛盾或模糊之处。
When domain experts use this LANGUAGE in discussions with developers or among themselves, they quickly discover areas where the model is inadequate for their needs or seems wrong to them. The domain experts (with the help of the developers) will also find areas where the precision of the model-based language exposes contradictions or vagueness in their thinking.
开发人员和领域专家可以通过逐步演练场景,使用模型对象来非正式地测试模型。几乎每一次讨论都是开发人员和用户专家共同体验模型的机会,他们可以在此过程中加深彼此的理解并完善概念。
The developers and domain experts can informally test the model by walking through scenarios, using the model objects step-by-step. Almost every discussion is an opportunity for the developers and user experts to play with the model together, deepening each other’s understanding and refining concepts as they go.
领域专家可以使用模型的语言来编写用例,并且可以通过指定验收测试来更直接地与模型互动。
The domain experts can use the language of the model in writing use cases, and can work even more directly with the model by specifying acceptance tests.
有时会有人反对使用模型语言来收集需求。毕竟,需求难道不应该独立于满足它们的设计吗?这种观点忽略了一个事实:所有语言都基于某种模型。词语的含义是难以捉摸的。领域模型通常会推导出……这些定义并非源自领域专家的惯用术语,而是经过“精简”,使其更加清晰、精准。当然,如果这些定义与该领域公认的含义有所出入,领域专家应该提出异议。在敏捷流程中,需求会随着项目的推进而不断演进,因为几乎不可能在项目启动之初就拥有足够的知识来充分描述一个应用程序。这种演进的一部分就应该是用更精炼的通用语言重新表述需求。
Objections are sometimes raised to the idea of using the language of the model to collect requirements. After all, shouldn’t requirements be independent of the design that fulfills them? This overlooks the reality that all language is based on some model. The meanings of words are slippery things. The domain model will typically derive from the domain experts’ own jargon but will have been “cleaned up,” to have sharper, narrower definitions. Of course, the domain experts should object if these definitions diverge from the meanings accepted in the field. In an Agile process, requirements evolve as a project goes along because hardly ever does the knowledge exist up front to specify an application adequately. Part of this evolution should be the reframing of the requirements in the refined UBIQUITOUS LANGUAGE.
多种语言的使用通常是必要的,但语言上的分歧绝不应该存在于领域专家和开发人员之间。(第 12 章“维护模型完整性”讨论了同一项目中模型的共存问题。)
Multiplicity of languages is often necessary, but the linguistic division should never be between the domain experts and the developers. (Chapter 12, “Maintaining Model Integrity,” deals with the coexistence of models on the same project.)
当然,开发人员会使用领域专家无法理解的技术术语。他们需要一套庞大的行话来讨论系统的技术细节。几乎可以肯定的是,用户也会使用一些专业术语,这些术语远远超出了应用程序的狭窄范围以及开发人员的理解范围。但这些都是语言的扩展。这些“方言”不应该包含反映不同模型的同一领域的替代词汇。
Of course, developers do use technical terminology that a domain expert wouldn’t understand. Developers have an extensive jargon that they need to discuss the technical aspects of a system. Almost certainly, the users will also have specialized jargon that goes well beyond the narrow scope of the application and the understanding of the developers. But these are extensions to the language. These dialects should not contain alternative vocabularies for the same domain that reflect distinct models.
图 2.3.普遍存在的语言是在行话的交汇处培养起来的。
Figure 2.3. UBIQUITOUS LANGUAGE is cultivated in the intersection of jargons.
借助通用语言,开发人员之间的对话、领域专家之间的讨论以及代码本身的表达都基于同一种语言,该语言源自共享的领域模型。
With a UBIQUITOUS LANGUAGE, conversations among developers, discussions among domain experts, and expressions in the code itself are all based on the same language, derived from a shared domain model.
每当我在开会讨论软件设计时,如果不画白板或草图,几乎无法集中精力。我画的大部分是UML图,主要是类图或对象交互图。
Whenever I’m in a meeting discussing a software design, I can hardly function without drawing on a whiteboard or sketchpad. A good part of what I draw is UML diagrams, mostly class diagrams or object-interactions.
有些人天生具有视觉思维,图表能帮助他们理解某些类型的信息。UML 图在传达对象之间的关系方面相当出色,也能很好地展示交互作用。但它们无法传达这些对象的概念定义。在会议中,我会一边绘制图表一边用语言解释这些概念的含义,或者在与其他参会者的对话中逐步阐明。
Some people are naturally visual, and diagrams help people grasp certain kinds of information. UML diagrams are pretty good at communicating relationships between objects, and they are fair at showing interactions. But they do not convey the conceptual definitions of those objects. In a meeting, I would flesh out those meanings in speech as I sketched the diagram, or they would emerge in a dialog with other participants.
简单、非正式的UML图可以有效地引导讨论。绘制一个包含三到五个与当前问题密切相关的对象的图表,就能帮助大家集中注意力。每个人都能理解对象之间的关系,以及对象的名称。有了图表的辅助,口头讨论会更加高效。随着大家尝试不同的思维实验,图表可以随时调整,草图也会像口头表达一样灵活多变,成为讨论中不可或缺的一部分。毕竟,UML代表的是统一建模语言(Unified Modeling Language)。
Simple, informal UML diagrams can anchor a discussion. Sketch a diagram of three to five objects central to the issue at hand, and everyone can stay focused. Everyone will share a view of the relationships between the objects and, significantly, the objects’ names. The spoken discussion can be more effective with this aid. A diagram can be changed as people try different thought experiments, and the sketch will take on some of the fluidity of spoken words, a true part of the discussion. After all, UML stands for Unified Modeling Language.
问题在于,人们常常觉得必须用 UML 来传达整个模型或设计。很多对象模型图过于详尽,同时又遗漏了太多信息。之所以过于详尽,是因为人们觉得必须把所有要编写代码的对象都放进建模工具里。结果就是,细节太多反而让人只见树木不见森林。
The trouble comes when people feel compelled to convey the whole model or design through UML. A lot of object model diagrams are too complete and, simultaneously, leave too much out. They are too complete because people feel they have to put all the objects that they are going to code into a modeling tool. With all that detail, no one can see the forest for the trees.
然而,尽管细节如此丰富,属性和关系仅仅是对象模型的一半。这些对象的行为以及它们所受到的约束并不容易用图形来展现。对象交互图可以展示设计中一些棘手的关键点,但大部分交互无法用这种方式呈现。无论是创建还是阅读这些图表,工作量都太大了。交互图仍然只能暗示模型背后的目的。为了包含约束和断言,UML 只能使用放在小括号中的文本,并将其插入到图中。
Yet in spite of all that detail, the attributes and relationships are only half the story of an object model. The behavior of those objects and the constraints on them are not so easily illustrated. Object interaction diagrams can illustrate some tricky hotspots in the design, but the bulk of the interactions can’t be shown that way. It is just too much work, both to create the diagrams and to read them. And an interaction diagram can still only imply the purpose behind the model. To include constraints and assertions, UML falls back on text, placed in little brackets, inserted into the diagram.
对象的行为职责可以通过操作名称暗示,也可以通过对象交互(或序列)图隐式地展示,但无法直接用文字描述。因此,这项任务需要借助补充文本或对话来完成。换句话说,UML 图无法传达模型的两个最重要方面:它所代表概念的含义,以及对象的预期功能。不过,这并不会给我们带来困扰,因为巧妙运用英语(或西班牙语或其他语言)就能很好地完成这项任务。
The behavioral responsibilities of an object can be hinted at through operation names, and they can be implicitly demonstrated with object interaction (or sequence) diagrams, but they cannot be stated. So, this task falls to supplemental text or conversation. In other words, a UML diagram cannot convey two of the most important aspects of a model: the meaning of the concepts it represents, and what the objects are meant to do. This needn’t trouble us, though, because careful use of English (or Spanish, or whatever) can fill this role pretty well.
UML 也不是一种令人满意的编程语言。我所见过的所有尝试利用建模工具的代码生成功能都适得其反。如果受限于 UML 的功能,你往往不得不舍弃模型中最关键的部分,因为它是一些无法用简单的框线图表达的规则。当然,代码生成器也无法利用这些文本注释。如果你使用某种允许用类似 UML 的图表语言编写可执行程序的技术,那么 UML 图就沦为另一种查看程序本身的方式,“模型”的真正含义也随之丧失。即使你使用 UML 作为实现语言,你仍然需要其他方法来传达清晰的模型。
Nor is UML a very satisfying programming language. Every attempt I’ve seen to use the code-generation capabilities of the modeling tools has been counterproductive. If you are constrained by the capabilities of UML, you will often have to leave out the most crucial part of the model because it is some rule that doesn’t fit into a box-and-line diagram. And, of course, a code generator cannot make use of those textual annotations. If you do use some technology that allows executable programs to be written in a UML-like diagramming language, then the UML diagram is reduced to merely another way to view the program itself, and the very meaning of “model” is lost. If you use UML as your implementation language, you will still need other means of communicating the uncluttered model.
图表是沟通和解释的工具,也有助于头脑风暴。简洁的图表最能发挥这些作用。涵盖整个对象模型的详尽图表无法有效地沟通和解释;它们会让读者感到信息过载,缺乏意义。这引导我们摒弃包罗万象的对象模型图,甚至摒弃UML包罗万象的数据库存储库图。它引导我们走向简化的图表,这些图表仅展示对象模型中概念上重要的部分,对于理解设计至关重要。本书中的图表是我在项目中使用的典型图表。它们力求简化、解释,甚至在阐明观点时会使用一些非标准符号。它们展示了设计约束,但并非详尽的设计规范。它们代表了概念的框架。
Diagrams are a means of communication and explanation, and they facilitate brainstorming. They serve these ends best if they are minimal. Comprehensive diagrams of the entire object model fail to communicate or explain; they overwhelm the reader with detail and they lack meaning. This leads us away from the all-encompassing object model diagram, or even the all-encompassing database repository of UML. It leads us toward simplified diagrams of conceptually important parts of the object model that are essential to understanding the design. The diagrams in this book are typical of those I use on projects. They simplify, they explain, and they even incorporate a bit of nonstandard notation when it clarifies their point. They show design constraints, but they are not design specifications in every detail. They represent the skeletons of ideas.
设计的关键细节都体现在代码中。一个编写良好的实现应该是透明的,能够展现其底层模型。(如何做到这一点是下一章以及本书其余部分的主要内容。)补充图表和文档可以引导读者关注核心要点。自然语言的讨论可以补充含义上的细微差别。这就是为什么我更喜欢反其道而行之,摒弃典型的 UML 图的处理方式。我并不绘制带有文字注释的图表,而是编写一份文本文档,并配以精选的简化图表。
The vital detail about the design is captured in the code. A well-written implementation should be transparent, revealing the model underlying it. (Making sure that this happens is the subject of the next chapter and much of the rest of this book.) Supplemental diagrams and documents can guide people’s attention to the central points. Natural language discussion can fill in the nuances of meaning. This is why I prefer to turn things inside out from the way a typical UML diagram handles them. Rather than a diagram annotated with text, I write a text document illustrated with selective and simplified diagrams.
请始终记住,模型并非图表。图表的目的是帮助沟通和解释模型。代码可以作为设计细节的存储库。编写良好的 Java 代码在某种程度上与 UML 一样富有表现力。精心选择和构建的图表,如果并非出于完整呈现模型或设计的执念而变得晦涩难懂,则可以起到集中注意力和辅助导航的作用。
Always remember that the model is not the diagram. The diagram’s purpose is to help communicate and explain the model. The code can serve as a repository of the details of the design. Well-written Java is as expressive as UML in its way. Carefully selected and constructed diagrams can serve to focus attention and aid navigation if they are not obscured by a compulsion to represent the model or design completely.
口头交流能够补充代码的严谨性和细节,赋予其更深层次的意义。虽然沟通对于将每个人与模型联系起来至关重要,但任何规模的团队都可能需要一些书面文档的稳定性和可共享性。然而,编写真正能够帮助团队开发出优秀软件的文档却是一项挑战。
Spoken communication supplements the code’s rigor and detail with meaning. But although talking is critical to connecting everyone to the model, a group of any size will probably need the stability and share-ability of some written documents. But making written documents that actually help the team produce good software is a challenge.
一旦文档定型,它往往会与项目流程失去联系。它会被代码的演变或项目语言的演变所淘汰。
Once a document takes on a persistent form, it often loses its connection with the flow of the project. It is left behind by the evolution of the code, or by the evolution of the language of the project.
多种方法都可行。本书第四部分稍后会推荐一些针对特定需求的具体文档,但我并不打算规定项目必须使用的文档集。相反,我将提供两条评估文档的通用准则。
Many approaches can work. A few specific documents will be suggested much later, in Part IV of this book, which address particular needs, but I make no attempt to prescribe a set of documents a project should use. Instead, I will offer two general guidelines for evaluating a document.
每个敏捷流程都有其自身的文档理念。极限编程提倡完全不使用任何额外的设计文档,让代码自己说话。运行中的代码不会说谎,不像其他任何文档那样。运行中的代码的行为是明确无误的。
Each Agile process has its own philosophy about documents. Extreme Programming advocates using no extra design documents at all and letting the code speak for itself. Running code doesn’t lie, as any other document might. The behavior of running code is unambiguous.
极限编程 (XP) 完全专注于程序的活跃元素和可执行测试。即使是添加到代码中的注释也不会影响程序的行为,因此它们总是与活跃代码及其驱动模型脱节。外部文档和图表也不会影响程序的行为,因此它们也与程序脱节。另一方面,口头交流和白板上的临时图表不会留下痕迹,因此不会造成混乱。这种对代码作为沟通媒介的依赖促使开发人员保持代码的简洁性和透明性。
Extreme Programming concentrates exclusively on the active elements of a program and executable tests. Even comments added to the code do not affect program behavior, so they always fall out of sync with the active code and its driving model. External documents and diagrams do not affect the behavior of the program, so they fall out of sync. On the other hand, spoken communication and ephemeral diagrams on whiteboards do not linger to create confusion. This dependence on the code as communication medium motivates developers to keep the code clean and transparent.
但代码作为设计文档确实存在局限性。它可能会用过多的细节让读者感到不知所措。虽然代码的行为是明确的,但这并不意味着它显而易见。而且,行为背后的含义可能难以传达。换句话说,完全通过代码进行文档记录与使用复杂的UML图存在一些相同的基本问题。当然,团队内部大量的口头交流可以提供代码的上下文和指导,但这些信息是短暂的、局部的。而且,需要理解模型的不仅仅是开发人员。
But code as a design document does have its limits. It can overwhelm the reader with detail. Although its behavior is unambiguous, that doesn’t mean it is obvious. And the meaning behind a behavior can be hard to convey. In other words, documenting exclusively through code has some of the same basic problems as using comprehensive UML diagrams. Of course, massive spoken communication within the team gives context and guidance around the code, but it is ephemeral and localized. And developers are not the only people who need to understand the model.
文档不应该试图做代码已经做得很好的事情。代码已经提供了所有细节,它精确地描述了程序的行为。
A document shouldn’t try to do what the code already does well. The code already supplies the detail. It is an exact specification of program behavior.
其他文档需要阐明含义,深入剖析大型结构,并将注意力集中在核心要素上。当编程语言不支持直接实现某个概念时,文档可以澄清设计意图。书面文档应与代码和口头交流相辅相成。
Other documents need to illuminate meaning, to give insight into large-scale structures, and to focus attention on core elements. Documents can clarify design intent when the programming language does not support a straightforward implementation of a concept. Written documents should complement the code and the talking.
当我用文字记录模型时,我会绘制模型中精心挑选的小子集,并用文字加以概括。我会用文字定义类及其职责,并尽可能用自然语言赋予它们意义。但图表也展现了在将概念形式化并精简为对象模型的过程中所做的一些选择。这些图表可以比较随意,甚至是手绘的。除了节省人力之外,手绘图表具有随意和临时性的优势。这些特点很适合用来沟通,因为它们通常也符合我们模型构想的实际情况。
When I document a model in writing, I diagram small, carefully selected subsets of the model and surround them with text. I define the classes and their responsibilities in words and frame them in a context of meaning as only a natural language can. But the diagram shows some of the choices that have been made in formalizing and paring down the concepts into an object model. These diagrams can be somewhat casual—even hand-drawn. In addition to saving labor, hand-drawn diagrams have the advantage of feeling casual and temporary. These are good things to communicate because they are generally true of our model ideas.
设计文档最大的价值在于解释模型的概念,帮助理解代码细节,并可能提供一些关于模型预期使用方式的见解。根据团队的理念,整个设计文档可以像贴在墙上的一系列草图一样简单,也可以非常详尽。
The greatest value of a design document is to explain the concepts of the model, help in navigating the detail of the code, and perhaps give some insight into the model’s intended style of use. Depending on the philosophy of the team, the whole design document could be as simple as a set of sketches posted on the walls, or it could be substantial.
文档必须参与项目活动。判断这一点的最简单方法是观察文档与通用语言的交互情况。文档是用项目团队成员(现在)使用的语言编写的吗?还是用代码中嵌入的语言编写的?
A document must be involved in project activities. The easiest way to judge this is to observe the document’s interaction with the UBIQUITOUS LANGUAGE. Is the document written in the language people speak on the project (now)? Is it written in the language embedded in the code?
倾听普遍使用的语言及其变化。如果设计文档中解释的术语没有开始出现在对话和代码中,那么这份文档就失去了意义。或许文档篇幅过长或过于复杂,或许它关注的主题不够重要,又或许人们要么根本没读过,要么觉得它缺乏吸引力。如果它对普遍使用的语言没有任何影响,那就说明有问题了。
Listen to the UBIQUITOUS LANGUAGE and how it is changing. If the terms explained in a design document don’t start showing up in conversations and code, the document is not fulfilling its purpose. Maybe the document is too big or complicated. Maybe it is not focused on a sufficiently important topic. People are either not reading it or not finding it compelling. If it is having no impact on the UBIQUITOUS LANGUAGE, something is wrong.
反之,你可能会听到一些普遍使用的语言在文档被弃置时自然而然地发生变化。显然,这份文档对人们来说似乎无关紧要,或者不够重要到需要更新。它可以安全地作为历史资料存档,但如果继续使用,可能会造成混乱,损害项目。如果一份文档没有发挥重要作用,仅仅依靠意志力和自律来更新它只会浪费精力。
Conversely, you may hear the UBIQUITOUS LANGUAGE changing naturally while a document is being left behind. Evidently the document does not seem relevant to people or does not seem important enough to update. It could safely be archived as history, but left active it could create confusion and hurt the project. And if a document isn’t playing an important role, keeping it up to date through sheer will and discipline wastes effort.
通用语言使得其他文档(例如需求规格说明书)更加简洁明了。随着领域模型逐渐反映出业务中最相关的知识,应用程序需求就成为该模型中的场景,而通用语言可以用来描述这些场景,其描述方式与模型驱动设计(参见第3章)直接相关。因此,规格说明书可以编写得更加简洁,因为它们无需传达模型背后的业务知识。
The UBIQUITOUS LANGUAGE allows other documents, such as requirements specifications, to be more concise and less ambiguous. As the domain model comes to reflect the most relevant knowledge of the business, application requirements become scenarios within that model, and the UBIQUITOUS LANGUAGE can be used to describe such a scenario in terms that directly connect to the MODEL-DRIVEN DESIGN (see Chapter 3). As a result, specifications can be written more simply, because they do not have to convey the business knowledge that lies behind the model.
通过精简文档并使其专注于补充代码和对话,文档可以与项目保持紧密联系。让通用语言及其发展演变成为您选择文档的指南,使文档能够融入并贯穿于项目活动中。
By keeping documents minimal and focusing them on complementing code and conversation, documents can stay connected to the project. Let the UBIQUITOUS LANGUAGE and its evolution be your guide to choosing documents that live and get woven into the project’s activity.
现在让我们来探讨一下极限编程(XP)社区和其他一些群体选择几乎完全依赖可执行代码及其测试的原因。本书的大部分内容都在讨论如何通过模型驱动设计(参见第三章)使代码传达意义。编写良好的代码可以非常清晰易懂,但它所传达的信息并不能保证准确无误。当然,一段代码实际导致的行为是无法回避的。但是,与方法的内部实现相比,方法名可能存在歧义、误导性或过时的情况。测试中的断言是严谨的,但变量名和代码组织所传达的信息却并非如此。良好的编程风格尽可能地保持这种联系的直接性,但这仍然需要自律。编写出不仅能做正确的事情,还能表达正确含义的代码,需要一丝不苟的细致。
Now let’s examine the choice of the XP community and some others, to rely almost exclusively on the executable code and its tests. Much of this book discusses ways to make the code convey meaning through a MODEL-DRIVEN DESIGN (see Chapter 3). Well-written code can be very communicative, but the message it communicates is not guaranteed to be accurate. Oh, the reality of the behavior caused by a section of code is inescapable. But a method name can be ambiguous, misleading, or out of date compared to the internals of the method. The assertions in a test are rigorous, but the story told by variable names and the organization of the code is not. Good programming style keeps this connection as direct as possible, but it is still an exercise in self-discipline. It takes fastidiousness to write code that doesn’t just do the right thing but also says the right thing.
消除这些差异是声明式设计(第 10 章讨论)等方法的主要卖点,在声明式设计中,程序元素的用途说明决定了其在程序中的实际行为。从 UML 生成程序的动力部分源于此,尽管到目前为止,这种方法总体上效果并不理想。
Elimination of those discrepancies is a major selling point of approaches such as declarative design (discussed in Chapter 10), in which a statement of the purpose of a program element determines its actual behavior in the program. The drive to generate programs from UML is partly motivated by this, though it generally hasn’t worked out well so far.
尽管代码也可能产生误导,但它比其他文档更贴近实际。要使代码的行为、意图和信息与当前标准技术保持一致,需要严谨的规范和特定的设计思维方式(详见第三部分)。为了有效沟通,代码必须使用与编写需求时相同的语言——也就是开发人员彼此之间以及与领域专家交流时使用的语言。
Still, while even code can mislead, it is closer to the ground than other documents. Aligning the behavior, intent, and message of code using current standard technology requires discipline and a certain way of thinking about design (discussed at length in Part III). To communicate effectively, the code must be based on the same language used to write the requirements—the same language that the developers speak with each other and with domain experts.
本书的主旨是,实施、设计和团队沟通应以同一模型为基础。为这些不同目的分别使用不同的模型会带来风险。
The thrust of this book is that one model should underlie implementation, design, and team communication. Having separate models for these separate purposes poses a hazard.
模型也可以作为教学辅助工具,用于教授特定领域知识。驱动设计的模型只是该领域的一种视角,但为了更好地理解该领域的通用知识,可以使用其他视角,这些视角仅作为教学工具。为此,人们可以使用图片或文字来传达与软件设计无关的其他类型的模型。
Models can also be valuable as education aids to teach about the domain. The model that drives the design is one view of the domain, but it may aid learning to have other views, used only as educational tools, to communicate general knowledge of the domain. For this purpose, people can use pictures or words that convey other kinds of models unrelated to software design.
需要其他模型的一个具体原因是其范围。驱动软件开发过程的技术模型必须严格精简到满足其功能所需的最低限度。解释性模型可以包含领域内的其他方面,从而提供背景信息,阐明范围更窄的技术模型。
One particular reason that other models are needed is scope. The technical model that drives the software development process must be strictly pared down to the necessary minimum to fulfill its functions. An explanatory model can include aspects of the domain that provide context that clarifies the more narrowly scoped model.
解释模型赋予我们更大的自由度,可以创建更具沟通性的风格,并针对特定主题进行定制。领域专家常用的视觉隐喻往往能提供更清晰的解释,从而帮助开发者理解并协调专家之间的意见。此外,解释模型以一种独特的方式呈现领域知识,而多元化的解释也有助于人们学习。
Explanatory models offer the freedom to create much more communicative styles tailored to a particular topic. Visual metaphors used by the domain experts in a field often present clearer explanations, educating developers and harmonizing experts. Explanatory models also present the domain in a way that is simply different, and multiple, diverse explanations help people learn.
解释性模型无需是对象模型,通常最好不要是。实际上,在这些模型中避免使用 UML 是有益的,可以避免与软件设计产生任何错误的对应关系。尽管解释性模型和驱动设计的模型通常确实存在对应关系,但相似之处很少是完全相同的。为了避免混淆,每个人都必须意识到这种区别。
There is no need for explanatory models to be object models, and it is generally best if they are not. It is actually helpful to avoid UML in these models, to avoid any false impression of correspondence with the software design. Even though the explanatory model and the model that drives design do often correspond, the similarities will seldom be exact. To avoid confusion, everyone must be conscious of the distinction.
设想一个为航运公司追踪货物的应用程序。该模型详细展示了如何将港口作业和船舶航程整合到货物运输的运营计划(“路线”)中。但对于新手来说,类图可能并不那么直观易懂。
Consider an application that tracks cargos for a shipping company. The model includes a detailed view of how port operations and vessel voyages are assembled into an operational plan for a cargo (a “route”). But to the uninitiated, a class diagram may not be very illuminating.
图 2.4. 运输路线的类图
Figure 2.4. A class diagram for a shipping route
在这种情况下,解释性模型可以帮助团队成员理解类图的实际含义。以下是看待相同概念的另一种方式:
In such a case, an explanatory model can help team members understand what the class diagram actually means. Here is another way of looking at the same concepts:
图 2.5中的每一条线分别代表港口作业(装卸货物)、地面堆放的货物或航行中船上的货物。这与类图并不完全一致,但它强化了领域学中的关键概念。
Each line in Figure 2.5 represents either a port operation (loading or unloading the cargo), or cargo sitting in storage on the ground, or cargo sitting on a ship en route. This does not correspond in detail with the class diagram, but it reinforces key points from the domain.
图 2.5. 航运路线的解释模型
Figure 2.5. An explanatory model for a shipping route
这种图表,结合对模型所代表的自然语言解释,可以帮助开发人员和领域专家理解更严谨的软件模型图。两者结合使用比单独使用任何一种都更容易理解。
This sort of diagram, along with natural language explanations of the model it represents, can help developers and domain experts alike understand the more rigorous software model diagrams. Together they are easier to understand than either view alone.
我一进门就看到一张完整的类图,打印在大张纸上,铺满了整面墙。这是我参与这个项目的第一天,一群聪明人花了几个月的时间,精心研究并构建了一个详尽的领域模型。模型中的典型对象与三四个其他对象有着错综复杂的关联,这种关联网络几乎没有明显的边界。从这个角度来看,分析师们确实忠实地把握了领域的本质。
The first thing I saw as I walked through the door was a complete class diagram printed on large sheets of paper that covered a large wall. It was my first day on a project in which smart people had spent months carefully researching and developing a detailed model of the domain. The typical object in the model had intricate associations with three or four other objects, and this web of associations had few natural borders. In this respect, the analysts had been true to the nature of the domain.
尽管那张巨幅图表令人眼花缭乱,但这个模型确实包含了一些知识。经过一段时间的学习,我学到了不少东西(尽管这种学习很难有方向性——就像漫无目的地浏览网页一样)。更令我沮丧的是,我的学习并没有让我深入了解应用程序的代码和设计。
As overwhelming as the wall-size diagram was, the model did capture some knowledge. After a moderate amount of study, I learned quite a bit (though that learning was hard to direct—much like randomly browsing the Web). I was more troubled to find that my study gave no insight into the application’s code and design.
当开发人员开始实现该应用程序时,他们很快发现,尽管人工分析师可以梳理错综复杂的关联关系,但这些关联关系却无法转化为可存储、可检索且具有事务完整性的单元。需要注意的是,该项目使用的是对象数据库,因此开发人员甚至无需面对将对象映射到关系表的挑战。从根本上讲,该模型并未提供任何实现指南。
When the developers had begun implementing the application, they had quickly discovered that the tangle of associations, although navigable by a human analyst, didn’t translate into storable, retrievable units that could be manipulated with transactional integrity. Mind you, this project was using an object database, so the developers didn’t even have to face the challenges of mapping objects into relational tables. At a fundamental level, the model did not provide a guide to implementation.
由于该模型是“正确的”,是技术分析师和业务专家广泛合作的成果,开发人员得出结论:基于概念的对象不能……成为他们设计的基础。于是,他们着手开发一个临时设计。他们的设计确实使用了一些相同的类名和数据存储属性,但它并非基于现有模型,甚至也不是基于任何模型。
Because the model was “correct,” the result of extensive collaboration between technical analysts and business experts, the developers reached the conclusion that conceptually based objects could not be the foundation of their design. So they proceeded to develop an ad hoc design. Their design did use a few of the same class names and attributes for data storage, but it was not based on the existing, or any, model.
该项目有一个领域模型,但是如果模型不能直接帮助开发运行中的软件,那么纸上的模型又有什么用呢?
The project had a domain model, but what good is a model on paper unless it directly aids the development of running software?
几年后,我在一个完全不同的流程中看到了同样的结果。这个项目是用Java实现的新设计替换现有的C++应用程序。旧应用程序完全是拼凑而成,根本没有考虑对象建模。如果说旧应用程序有什么设计的话,那就是在现有代码之上不断添加新功能,而没有任何明显的通用化或抽象。
A few years later, I saw the same end result come from a completely different process. This project was to replace an existing C++ application with a new design implemented in Java. The old application had been hacked together without any regard for object modeling. The design of the old application, if there was one, had accreted as one capability after another had been laid on top of the existing code, without any noticeable generalization or abstraction.
令人毛骨悚然的是,这两个流程的最终产品竟然非常相似!两者都具备功能,但都臃肿不堪、难以理解,最终都无法维护。尽管在某些实现方式上,它们展现出某种直接性,但你却无法通过阅读代码深入了解系统的用途。除了用作一些花哨的数据结构之外,这两个流程都没有充分利用其开发环境中可用的对象范式。
The eerie thing was that the end products of the two processes were very similar! Both had functionality, but were bloated, very hard to understand, and eventually unmaintainable. Though the implementations had, in places, a kind of directness, you couldn’t gain much insight about the purpose of the system by reading the code. Neither process took any advantage of the object paradigm available in their development environment, except as fancy data structures.
模型种类繁多,用途广泛,即使在软件开发项目的背景下也是如此。领域驱动设计要求模型不仅有助于早期分析,而且是设计的基础。这种方法对代码有着重要的影响。不太明显的是,领域驱动设计需要一种不同的建模方法……
Models come in many varieties and serve many roles, even those restricted to the context of a software development project. Domain-driven design calls for a model that doesn’t just aid early analysis but is the very foundation of the design. This approach has some important implications for the code. What is less obvious is that domain-driven design requires a different approach to modeling. . . .
星盘是用来计算恒星位置的,它是天空模型的机械实现。
The astrolabe, used to compute star positions, is a mechanical implementation of a model of the sky.
将代码与底层模型紧密联系起来,就能赋予代码意义,并使模型具有相关性。
Tightly relating the code to an underlying model gives the code meaning and makes the model relevant.
如果项目完全没有领域模型,只是编写代码来实现一个又一个功能,那么它们就很难获得前两章讨论的知识积累和沟通方面的优势。复杂的领域会让它们不堪重负。
Projects that have no domain model at all, but just write code to fulfill one function after another, gain few of the advantages of knowledge crunching and communication discussed in the previous two chapters. A complex domain will swamp them.
另一方面,许多复杂的项目虽然尝试构建某种领域模型,但却未能将模型与代码紧密结合。他们开发的模型,或许在初期可以作为探索性工具,但随着时间的推移,会变得越来越不相关,甚至产生误导。尽管在模型上倾注了大量心血,却无法保证设计的正确性,因为两者并不一致。
On the other hand, many complex projects do attempt some sort of domain model, but they don’t maintain a tight connection between the model and the code. The model they develop, possibly useful as an exploratory tool at the outset, becomes increasingly irrelevant and even misleading. All the care lavished on the model provides little reassurance that the design is correct, because the two are different.
这种联系可能以多种方式断裂,但这种分离通常是有意识的选择。许多设计方法论都提倡使用分析模型,该模型与设计截然不同,通常由不同的人员开发。之所以称之为分析模型,是因为它是分析业务领域并组织其概念的产物,而没有考虑它在软件系统中将扮演的角色。分析模型仅作为一种理解工具;将实施方面的考虑因素混入设计中,会使问题更加复杂。之后,设计方案可能与分析模型只有松散的对应关系。分析模型在创建时并未考虑设计问题,因此很可能无法满足实际需求。
This connection can break down in many ways, but the detachment is often a conscious choice. Many design methodologies advocate an analysis model, quite distinct from the design and usually developed by different people. It is called an analysis model because it is the product of analyzing the business domain to organize its concepts without any consideration of the part it will play in a software system. An analysis model is meant as a tool for understanding only; mixing in implementation concerns is thought to muddy the waters. Later, a design is created that may have only a loose correspondence to the analysis model. The analysis model is not created with design issues in mind, and therefore it is likely to be quite impractical for those needs.
在分析过程中确实会进行一些知识运算,但大部分知识会在编码阶段丢失,因为开发人员被迫为设计提出新的抽象概念。此时,无法保证分析师获得并嵌入模型中的洞见能够被保留或重新发现。因此,此时维护设计与松散连接的模型之间的任何映射关系都得不偿失。
Some knowledge crunching happens during such an analysis, but most of it is lost when coding begins, when the developers are forced to come up with new abstractions for the design. Then there is no guarantee that the insights gained by the analysts and embedded in the model will be retained or rediscovered. At this point, maintaining any mapping between the design and the loosely connected model is not cost-effective.
纯粹的分析模型甚至无法实现其理解领域的主要目标,因为关键的发现总会在设计/实现过程中涌现。非常具体且意想不到的问题也总是会出现。预先构建的模型会深入探讨一些无关紧要的主题,而忽略一些重要的主题。其他一些主题的呈现方式对应用程序来说毫无用处。结果是,纯粹的分析模型在编码开始后不久就会被弃用,大部分工作不得不重新来过。但是,如果开发人员将分析视为一个独立的过程,那么第二次建模就会变得不够规范。如果管理人员将分析视为一个独立的过程,那么开发团队可能无法获得足够的领域专家支持。
The pure analysis model even falls short of its primary goal of understanding the domain, because crucial discoveries always emerge during the design/implementation effort. Very specific, unanticipated problems always arise. An up-front model will go into depth about some irrelevant subjects, while it overlooks some important subjects. Other subjects will be represented in ways that are not useful to the application. The result is that pure analysis models get abandoned soon after coding starts, and most of the ground has to be covered again. But the second time around, if the developers perceive analysis to be a separate process, modeling happens in a less disciplined way. If the managers perceive analysis to be a separate process, the development team may not be given adequate access to domain experts.
无论原因是什么,缺乏设计理念的软件充其量只是一个能够完成一些有用事情却不解释其行为的机制。
Whatever the cause, software that lacks a concept at the foundation of its design is, at best, a mechanism that does useful things without explaining its actions.
如果设计或其核心部分与领域模型不匹配,那么该模型就几乎毫无价值,软件的正确性也令人怀疑。同时,模型与设计功能之间复杂的映射关系难以理解,而且在实践中,随着设计的变更,这种映射关系也难以维护。分析和设计之间由此产生了致命的鸿沟,导致两者之间无法相互借鉴和融合。
If the design, or some central part of it, does not map to the domain model, that model is of little value, and the correctness of the software is suspect. At the same time, complex mappings between models and design functions are difficult to understand and, in practice, impossible to maintain as the design changes. A deadly divide opens between analysis and design so that insight gained in each of those activities does not feed into the other.
分析必须以易于理解、表达清晰的方式捕捉领域内的基本概念。设计必须明确一组可以通过编程构建的组件。项目中使用的工具能够在目标部署环境中高效运行,并能正确解决应用程序提出的问题。
An analysis must capture fundamental concepts from the domain in a comprehensible, expressive way. The design has to specify a set of components that can be constructed with the programming tools in use on the project that will perform efficiently in the target deployment environment and will correctly solve the problems posed for the application.
模型驱动设计摒弃了分析模型和设计二元对立的传统观念,转而寻求一个能够同时满足分析和设计需求的单一模型。抛开纯粹的技术问题,设计中的每个对象都扮演着模型中描述的概念性角色。这就要求我们对所选模型提出更高的要求,因为它必须满足两个截然不同的目标。
MODEL-DRIVEN DESIGN discards the dichotomy of analysis model and design to search out a single model that serves both purposes. Setting aside purely technical issues, each object in the design plays a conceptual role described in the model. This requires us to be more demanding of the chosen model, since it must fulfill two quite different objectives.
对领域进行抽象的方法总是多种多样,解决应用问题的设计方案也总是多种多样。正因如此,将模型与设计结合起来才显得尤为重要。然而,这种结合绝不能以削弱分析能力为代价,更不能因技术考量而导致分析能力的严重缺陷。我们也不能接受那些笨拙的设计方案,它们虽然反映了领域概念,却违背了软件设计原则。这种方法要求模型既能有效地进行分析,又能有效地进行设计。当一个模型似乎不适用于实际应用时,我们必须寻找新的模型。当一个模型未能忠实地表达领域的关键概念时,我们也必须寻找新的模型。如此一来,建模和设计过程便成为一个迭代循环。
There are always many ways of abstracting a domain, and there are always many designs that can solve an application problem. This is what makes it practical to bind the model and design. This binding mustn’t come at the cost of a weakened analysis, fatally compromised by technical considerations. Nor can we accept clumsy designs, reflecting domain ideas but eschewing software design principles. This approach demands a model that works well as both analysis and design. When a model doesn’t seem to be practical for implementation, we must search for a new one. When a model doesn’t faithfully express the key concepts of the domain, we must search for a new one. The modeling and design process then becomes a single iterative loop.
将领域模型与设计紧密联系起来的必要性,为从众多可能的模型中选择更有用的模型增添了一个新的标准。这需要深入思考,通常需要多次迭代和大量的重构,但它能使模型更具针对性。
The imperative to relate the domain model closely to the design adds one more criterion for choosing the more useful models out of the universe of possible models. It calls for hard thinking and usually takes multiple iterations and a lot of refactoring, but it makes the model relevant.
所以:
Therefore:
设计软件系统的一部分,使其以非常直接的方式反映领域模型,从而使映射关系一目了然。重新审视该模型并对其进行修改,使其更自然地应用于软件开发,同时力求使其反映对领域的更深层次理解。要求构建一个能够很好地满足这两个目的的单一模型,并支持一种强大的普适语言。
Design a portion of the software system to reflect the domain model in a very literal way, so that mapping is obvious. Revisit the model and modify it to be implemented more naturally in software, even as you seek to make it reflect deeper insight into the domain. Demand a single model that serves both purposes well, in addition to supporting a robust UBIQUITOUS LANGUAGE.
从模型中提取设计中使用的术语和基本职责分配。代码是模型的体现,因此对代码的任何更改都可能意味着对模型的更改。这种更改的影响必须相应地波及到项目的其他活动。
Draw from the model the terminology used in the design and the basic assignment of responsibilities. The code becomes an expression of the model, so a change to the code may be a change to the model. Its effect must ripple through the rest of the project’s activities accordingly.
要使实现完全依赖于模型,通常需要支持建模范式的软件开发工具和语言,例如面向对象编程。
To tie the implementation slavishly to a model usually requires software development tools and languages that support a modeling paradigm, such as object-oriented programming.
有时不同的子系统会有不同的模型(参见第 14 章),但对于系统的特定部分,从代码到需求分析的整个开发过程中,应该只使用一个模型。
Sometimes there will be different models for different subsystems (see Chapter 14), but only one model should apply to a particular part of the system, throughout all aspects of the development effort, from the code to requirements analysis.
单一模型降低了出错的概率,因为设计现在是经过深思熟虑的模型的直接延伸。设计,甚至代码本身,都具有模型的沟通性。
The single model reduces the chances of error, because the design is now a direct outgrowth of the carefully considered model. The design, and even the code itself, has the communicativeness of a model.
开发一个既能捕捉问题又能提供实用设计的单一模型,说起来容易做起来难。你不能随便拿一个模型就把它变成可行的设计。模型必须经过精心设计,才能实现实际应用。设计和实现技术必须能够让代码有效地表达模型(参见第二部分)。知识分析师探索各种模型选项,并将它们提炼成实用的软件元素。开发过程变成了一个迭代过程,将模型、设计和代码作为一个整体进行改进(参见第三部分)。
Developing a single model that captures the problem and provides a practical design is easier said than done. You can’t just take any model and turn it into a workable design. The model has to be carefully crafted to make for a practical implementation. Design and implementation techniques have to be employed that allow code to express a model effectively (see Part II). Knowledge crunchers explore model options and refine them into practical software elements. Development becomes an iterative process of refining the model, the design, and the code as a single activity (see Part III).
要使模型驱动设计取得成功,模型与设计之间的对应关系必须非常精确,误差范围在人为误差以内。为了实现模型与设计之间如此紧密的对应关系,几乎必须采用一种建模范式,并辅以相应的软件工具,以便创建与模型概念直接对应的实例。
To make a MODEL-DRIVEN DESIGN pay off, the correspondence must be literal, exact within bounds of human error. To make such a close correspondence of model and design possible, it is almost essential to work within a modeling paradigm supported by software tools that allow you to create direct analogs to the concepts in the model.
图 3.1
Figure 3.1
面向对象编程之所以强大,是因为它基于建模范式,并提供了模型构造的实现。对程序员而言,对象真实地存在于内存中,它们与其他对象关联,被组织成类,并通过消息传递提供行为。尽管许多开发者仅仅受益于应用对象的技术能力来组织程序代码,但对象设计的真正突破在于代码能够表达模型的概念。Java 和许多其他工具允许创建与概念对象模型直接对应的对象和关系。
Object-oriented programming is powerful because it is based on a modeling paradigm, and it provides implementations of the model constructs. As far as the programmer is concerned, objects really exist in memory, they have associations with other objects, they are organized into classes, and they provide behavior available by messaging. Although many developers benefit from just applying the technical capabilities of objects to organize program code, the real breakthrough of object design comes when the code expresses the concepts of a model. Java and many other tools allow the creation of objects and relationships directly analogous to conceptual object models.
尽管Prolog语言从未像面向对象语言那样被广泛使用,但它天然适合模型驱动设计。在模型驱动设计中,范式是逻辑,模型是一组逻辑规则和事实,逻辑规则和事实是它们所操作的基础。
Although it has never reached the mass usage that object-oriented languages have, the Prolog language is a natural fit for MODEL-DRIVEN DESIGN. In this case, the paradigm is logic, and the model is a set of logical rules and facts they operate on.
模型驱动设计在使用C语言等编程语言时适用性有限,因为没有与纯过程式语言相对应的建模范式。这些语言之所以是过程式的,是因为程序员告诉计算机一系列要执行的步骤。尽管程序员可能在思考领域概念,但程序本身只是一系列对数据的技术操作。结果可能有用,但程序本身并没有捕捉到多少意义。过程式语言通常支持复杂的数据类型,这些数据类型开始与更自然的领域概念相对应,但这些复杂类型仅仅是组织化的数据,它们无法捕捉领域的动态特性。因此,用过程式语言编写的软件,其复杂的函数是基于预期的执行路径而非领域模型中的概念联系而相互连接的。
MODEL-DRIVEN DESIGN has limited applicability using languages such as C, because there is no modeling paradigm that corresponds to a purely procedural language. Those languages are procedural in the sense that the programmer tells the computer a series of steps to follow. Although the programmer may be thinking about the concepts of the domain, the program itself is a series of technical manipulations of data. The result may be useful, but the program doesn’t capture much of the meaning. Procedural languages often support complex data types that begin to correspond to more natural conceptions of the domain, but these complex types are only organized data, and they don’t capture the active aspects of the domain. The result is that software written in procedural languages has complicated functions linked together based on anticipated paths of execution, rather than by conceptual connections in the domain model.
在我听说面向对象编程之前,我曾用FORTRAN编写程序来解决数学模型,而这正是FORTRAN 的优势领域。数学函数是此类模型的主要概念组成部分,可以用FORTRAN清晰地表达。即便如此,也无法捕捉函数之外的更高层次的含义。大多数非数学领域并不适合采用过程式模型驱动设计。语言,因为这些领域没有被概念化为数学函数或程序中的步骤。
Before I ever heard of object-oriented programming, I wrote FORTRAN programs to solve mathematical models, which is just the sort of domain in which FORTRAN excels. Mathematical functions are the main conceptual component of such a model and can be cleanly expressed in FORTRAN. Even so, there is no way to capture higher level meaning beyond the functions. Most non-mathematical domains don’t lend themselves to MODEL-DRIVEN DESIGN in procedural languages because the domains are not conceptualized as math functions or as steps in a procedure.
面向对象设计是目前大多数雄心勃勃的项目所采用的范式,也是本书主要采用的方法。
Object-oriented design, the paradigm that currently dominates the majority of ambitious projects, is the approach used primarily in this book.
正如第一章所述,印刷电路板 (PCB) 可以看作是由连接各种元件引脚的导电网络(称为网)组成的集合。通常有数万条网。一种称为 PCB 布局工具的专用软件会为所有网找到一种物理排列方式,使它们不会交叉或相互干扰。它通过优化网的路径来实现这一点,同时还要满足设计人员设定的大量限制条件,这些限制条件限制了网的布局方式。尽管 PCB 布局工具非常先进,但它们仍然存在一些不足之处。
As discussed in Chapter 1, a printed circuit board (PCB) can be viewed as a collection of electrical conductors (called nets) connecting the pins of various components. There are often tens of thousands of nets. Special software, called a PCB layout tool, finds a physical arrangement for all the nets so that they don’t cross or interfere with each other. It does this by optimizing their paths while satisfying an enormous number of constraints placed by the human designers that restrict the way they can be laid out. Although PCB layout tools are very sophisticated, they still have some shortcomings.
问题之一在于,这数千个网络中的每一个都有自己的一套布局规则。PCB工程师认为许多网络属于自然分组,这些分组应该遵循相同的规则。例如,一些网络构成总线。
One problem is that each of these thousands of nets has its own set of layout rules. PCB engineers see many nets as belonging to natural groupings that should share the same rules. For example, some nets form buses.
图 3.2. 公交车和网络示意图
Figure 3.2. An explanatory diagram of buses and nets
通过将多个网状结构组合成一个总线(一次可能组合 8 个、16 个或 256 个),工程师可以将工作量缩小到更易于管理的规模,从而提高生产效率并减少错误。问题在于,布局工具存在以下问题:没有“公共汽车”这种概念。规则必须逐个分配给成千上万个网络。
By lumping nets into a bus, perhaps 8 or 16 or 256 at a time, the engineer cuts the job down to a more manageable size, improving productivity and reducing errors. The trouble is, the layout tool has no such concept as a bus. Rules have to be assigned to tens of thousands of nets, one net at a time.
走投无路的工程师们通过编写脚本来绕过布局工具的这一限制,这些脚本可以解析布局工具的数据文件,并将规则直接插入到文件中,一次将这些规则应用于整个总线。
Desperate engineers worked around this limitation in the layout tool by writing scripts that parse the layout tool’s data files and insert rules directly into the file, applying them to an entire bus at a time.
布局工具会将每个电路连接存储在一个网络列表文件中,该文件看起来像这样:
The layout tool stores each circuit connection in a net list file, which looks something like this:
Net Name Component.Pin
-------- -------------
Xyz0 A.0, B.0
Xyz1 A.1, B.1
Xyz2 A.2, B.2
. . .
它将布局规则存储在类似这样的文件格式中:
It stores the layout rules in a file format something like this:
Net Name Rule Type Parameters
-------- --------- ----------
Xyz1 min_linewidth 5
Xyz1 max_delay 15
Xyz2 min_linewidth 5
Xyz2 max_delay 15
. . .
工程师们精心设计了一套网络命名规则,以便按字母顺序对数据文件进行排序后,同一总线上的网络能够被放在一起。然后,他们的脚本可以解析该文件,并根据每个网络所属的总线对其进行修改。实际用于解析、操作和写入文件的代码过于冗长晦涩,不适合在此示例中使用,因此我将仅列出该过程的步骤。
The engineers carefully use a naming convention for the nets so that an alphabetical sort of the data file will place the nets of a bus together in a sorted file. Then their script can parse the file and modify each net based on its bus. Actual code to parse, manipulate, and write the files is just too verbose and opaque to serve this example, so I’ll just list the steps in the procedure.
1. Sort net list file by net name.
2. Read each line in file, seeking first one that starts with bus name pattern.
3. For each line with matching name, parse line to get net name.
4. Append net name with rule text to rules file.
5. Repeat from 3 until left of line no longer matches bus name.
So the input of a bus rule such as this:
Bus Name Rule Type Parameters
-------- --------- ----------
Xyz max_vias 3
这将导致向文件中添加类似这样的网络规则:
would result in adding net rules to the file like these:
Net Name Rule Type Parameters
-------- --------- ----------
. . .
Xyz0 max_vias 3
Xyz1 max_vias 3
Xyz2 max_vias 3
. . .
我猜想,最初编写这类脚本的人可能只有这么简单的需求,如果这是唯一的要求,那么这样的脚本就非常有意义。但实际上,现在已经有几十个类似的脚本了。当然,它们可以重构,共享排序和字符串匹配功能;如果编程语言支持函数调用来封装细节,脚本的编写方式或许就能像上面总结的步骤那样简洁明了。但即便如此,它们仍然只是文件操作。如果要使用不同的文件格式(而且文件格式有很多种),即使分组总线并应用规则的概念相同,也需要从头开始编写。如果想要更丰富的功能或交互性,就必须为此付出额外的代价。
I imagine that the person who first wrote such a script had only this simple need, and if this were the only requirement, a script like this would make a lot of sense. But in practice, there are now dozens of scripts. They could, of course, be refactored to share sorting and string matching functions, and if the language supported function calls to encapsulate the details, the scripts could begin to read almost like the summary steps above. But still, they are just file manipulations. A different file format (and there are several) would require starting from scratch, even though the concept of grouping buses and applying rules to them is the same. If you wanted richer functionality or interactivity, you would have to pay for every inch.
脚本编写者试图用“总线”的概念来补充工具的领域模型。他们的实现通过排序和字符串匹配来推断总线的存在,但并没有明确地处理这个概念。
What the script writers were trying to do was to supplement the tool’s domain model with the concept of “bus.” Their implementation infers the bus’s existence through sorts and string matches, but it does not explicitly deal with the concept.
前面的讨论已经描述了领域专家用来思考问题的概念。现在我们需要将这些概念明确地组织成一个模型,以便我们可以基于这个模型开发软件。
The preceding discussion has already described the concepts the domain experts use to think about their problems. Now we need to organize those concepts explicitly into a model we can base software on.
图 3.3. 面向高效布局规则分配的类图
Figure 3.3. A class diagram oriented toward efficient assignment of layout rules
如果这些对象是用面向对象的语言实现的,那么核心功能就变得几乎微不足道了。
With these objects implemented in an object-oriented language, the core functionality becomes almost trivial.
该assignRule()方法可在抽象网络上实现。网络assignedRules()上的方法接受自身的规则及其总线的规则。
The assignRule() method can be implemented on Abstract Net. The assignedRules() method on Net takes its own rules and its Bus’s rules.
abstract class AbstractNet {
private Set rules;
void assignRule(LayoutRule rule) {
rules.add(rule);
}
Set assignedRules() {
return rules;
}
}
class Net extends AbstractNet {
private Bus bus;
Set assignedRules() {
Set result = new HashSet();
result.addAll(super.assignedRules());
result.addAll(bus.assignedRules());
return result;
}
}
当然,还需要大量的辅助代码,但这涵盖了脚本的基本功能。
Of course, there would be a great deal of supporting code, but this covers the basic functionality of the script.
该应用程序需要导入/导出逻辑,我们将把它封装成一些简单的服务。
The application requires import/export logic, which we’ll encapsulate into some simple services.
我们还需要一些工具:
We’ll also need a few utilities:
现在,启动应用程序只需使用导入的数据初始化存储库即可:
Now, starting the application is a matter of initializing the repositories with imported data:
Collection nets = NetListImportService.read(aFile);
NetRepository.addAll(nets);
Collection buses = InferredBusFactory.groupIntoBuses(nets);
BusRepository.addAll(buses);
每个服务和存储库都可以进行单元测试。更重要的是,核心领域逻辑也可以进行测试。以下是使用 JUnit 测试框架对最核心行为进行的单元测试:
Each of the services and repositories can be unit-tested. Even more important, the core domain logic can be tested. Here is a unit test of the most central behavior (using the JUnit test framework):
public void testBusRuleAssignment() {
Net a0 = new Net("a0");
Net a1 = new Net("a1");
Bus a = new Bus("a"); //Bus is not conceptually dependent
a.addNet(a0); //on name-based recognition, and so
a.addNet(a1); //its tests should not be either.
NetRule minWidth4 = NetRule.create(MIN_WIDTH, 4);
a.assignRule(minWidth4);
assertTrue(a0.assignedRules().contains(minWidth4));
assertEquals(minWidth4, a0.getRule(MIN_WIDTH));
assertEquals(minWidth4, a1.getRule(MIN_WIDTH));
}
交互式用户界面可以显示公交车列表,允许用户为每条公交车分配规则;或者,为了向后兼容,它可以从规则文件中读取规则。外观设计使两种界面的访问都变得简单。它的实现方式与测试相呼应:
An interactive user interface could present a list of buses, allowing the user to assign rules to each, or it could read from a file of rules for backward compatibility. A façade makes access simple for either interface. Its implementation echoes the test:
public void assignBusRule(String busName, String ruleType,
double parameter){
Bus bus = BusRepository.getByName(busName);
bus.assignRule(NetRule.create(ruleType, parameter));
}
精加工:
Finishing:
NetRuleExport.write(aFileName, NetRepository.allNets());
(该服务会向每个网络assignedRules()请求信息,然后将它们完全展开后写入。)
(The service asks each Net for assignedRules(), and then writes them fully expanded.)
当然,如果只有一个操作(如示例所示),基于脚本的方法可能同样实用。但实际上,操作数量至少有 20 个。模型驱动设计易于扩展,并且可以包含规则组合约束和其他增强功能。
Of course, if there were only one operation (as in the example), the script-based approach might be just as practical. But in reality, there were 20 or more. The MODEL-DRIVEN DESIGN scales easily and can include constraints on combining rules and other enhancements.
第二种设计也便于测试。它的组件具有定义良好的接口,可以进行单元测试。测试脚本的唯一方法是进行端到端的文件输入/输出比较。
The second design also accommodates testing. Its components have well-defined interfaces that can be unit-tested. The only way to test the script is to do an end-to-end file-in/file-out comparison.
请记住,这样的设计并非一蹴而就。它需要经过多次重构和知识梳理,才能将领域的重要概念提炼成一个简洁明了的模型。
Keep in mind that such a design does not emerge in a single step. It would take several iterations of refactoring and knowledge crunching to distill the important concepts of the domain into a simple, incisive model.
理论上,或许你可以向用户展示系统的任何视图,而无需考虑其底层实际情况。但实际上,这种不匹配充其量会导致困惑,最坏的情况是导致程序错误。举个简单的例子,看看当前版本的微软 Internet Explorer 中叠加的网站书签模型是如何误导用户的。¹
In theory, perhaps, you could present a user with any view of a system, regardless of what lies beneath. But in practice, a mismatch causes confusion at best—bugs at worst. Consider a very simple example of how users are misled by superimposed models of bookmarks for Web sites in current releases of Microsoft Internet Explorer.1
Internet Explorer 用户通常认为“收藏夹”是一个网站名称列表,该列表会在会话之间保持不变。但实际上,IE 将收藏夹视为一个包含 URL 的文件,并将文件名添加到收藏夹列表中。如果网页标题包含 Windows 文件名中不允许的字符,就会出现问题。假设用户尝试保存一个收藏夹,并输入以下名称:“懒惰:幸福的秘诀”。此时会显示错误信息:“文件名不能包含以下任何字符:\ / : * ? " < > |”。这算什么文件名?另一方面,如果网页标题本身已经包含非法字符,Internet Explorer 会默默地将其删除。在这种情况下,数据丢失可能无关紧要,但这并非用户所期望的。在大多数应用程序中,默默地更改数据是完全不可接受的。
A user of Internet Explorer thinks of “Favorites” as a list of names of Web sites that persist from session to session. But the implementation treats a Favorite as a file containing a URL, and whose filename is put in the Favorites list. That’s a problem if the Web page title contains characters that are illegal in Windows filenames. Suppose a user tries to store a Favorite and types the following name for it: “Laziness: The Secret to Happiness”. An error message will say: “A filename cannot contain any of the following characters: \ / : * ? " < > | ”. What filename? On the other hand, if the Web page title already contains an illegal character, Internet Explorer will just quietly strip it out. The loss of data may be benign in this case, but not what the user would have expected. Quietly changing data is completely unacceptable in most applications.
模型驱动设计要求在任何单一情境下(这将在第14章讨论)只使用一个模型。大多数建议和示例都针对分析模型和设计模型各自独立的问题,但这里我们面临的问题来自另一对模型:用户模型和设计/实现模型。
MODEL-DRIVEN DESIGN calls for working with only one model (within any single context, as will be discussed in Chapter 14). Most of the advice and examples go to the problems of having separate analysis models and design models, but here we have a problem arising from a different pair of models: the user model and the design/implementation model.
当然,在大多数情况下,未经修饰的领域模型视图对用户来说肯定不方便。但是,除非这种错觉完美无缺,否则试图在用户界面中营造一种与领域模型不同的模型假象只会造成混淆。如果“Web收藏夹”实际上只是快捷方式文件的集合,那么就应该向用户明确说明这一点,并移除令人困惑的替代模型。这样不仅能减少用户的困惑,还能让他们利用自己对文件系统的了解来处理“Web收藏夹”。例如,他们可以使用文件资源管理器来重新组织这些快捷方式,而不是使用Web浏览器内置的笨拙工具。了解情况的用户将能够更好地利用将Web快捷方式存储在文件系统任意位置的灵活性。仅仅移除这种误导性的额外模型,就能提升应用程序的功能,使其更加清晰易懂。既然程序员认为旧模型已经足够好用,为什么还要让用户学习新模型呢?
Of course, an unadorned view of the domain model would definitely not be convenient for the user in most cases. But trying to create in the UI an illusion of a model other than the domain model will cause confusion unless the illusion is perfect. If Web Favorites are actually just a collection of shortcut files, then expose this fact to the user and eliminate the confusing alternative model. Not only will the feature be less confusing, but the user can then leverage what he knows about the file system to deal with Web Favorites. He can reorganize them with the File Explorer, for example, rather than use awkward tools built into the Web browser. Informed users would be more able to exploit the flexibility of storing Web shortcuts anywhere in the file system. Just by removing the misleading extra model, the power of the application would increase and become clearer. Why make the user learn a new model when the programmers felt the old model was good enough?
或者,也可以用其他方式存储收藏夹,例如存储在数据文件中,以便它们可以遵循自己的规则。这些规则大概就是适用于网页的命名规则。再次提供一个统一的模型。这个模型告诉用户,他所了解的关于网站命名的一切知识都适用于收藏夹。
Alternatively, store the Favorites in a different way, say in a data file, so that they can be subject to their own rules. Those rules would presumably be the naming rules that apply to Web pages. That would again provide a single model. This one tells the user that everything he knows about naming Web sites applies to Favorites.
当设计基于反映用户和领域专家基本需求的模型时,与其他设计方法相比,设计的框架能够更清晰地展现给用户。这种模型的展现使用户能够更好地了解软件的潜力,并获得一致且可预测的行为。
When a design is based on a model that reflects the basic concerns of the users and domain experts, the bones of the design can be revealed to the user to a greater extent than with other design approaches. Revealing the model gives the user more access to the potential of the software and yields consistent, predictable behavior.
制造业是软件开发中一个常见的比喻。这种比喻的一个推论是:技术精湛的工程师负责设计,而技术水平较低的工人负责组装产品。然而,这种比喻已经导致许多项目失败,原因很简单——软件开发本质上就是设计。虽然所有团队都有成员分工明确,但过度划分分析、建模、设计和编程的责任会阻碍模型驱动设计(MDD)。
Manufacturing is a popular metaphor for software development. One inference from this metaphor: highly skilled engineers design; less skilled laborers assemble the products. This metaphor has messed up a lot of projects for one simple reason—software development is all design. All teams have specialized roles for members, but overseparation of responsibility for analysis, modeling, design, and programming interferes with MODEL-DRIVEN DESIGN.
在一个项目中,我的工作是协调不同的应用团队,并协助开发驱动设计的领域模型。但管理层认为建模人员应该专注于建模,而编码是对这些技能的浪费,因此我实际上被禁止编写程序或与程序员一起处理细节。
On one project, my job was to coordinate different application teams and help develop the domain model that would drive the design. But the management thought that modelers should be modeling, and that coding was a waste of those skills, so I was in effect forbidden to program or work on details with programmers.
事情似乎一度进展顺利。我们与领域专家和各个团队的开发负责人合作,梳理知识,完善了一个不错的核心模型。但由于两个原因,这个模型从未投入使用。
Things seemed to be OK for a while. Working with domain experts and the development leads of the different teams, we crunched knowledge and refined a nice core model. But that model was never put to work, for two reasons.
首先,模型的部分意图在交接过程中丢失了。模型的整体效果对细节非常敏感(这将在第二部分和第三部分中讨论),而这些细节并不总是能在UML图或一般性讨论中体现出来。如果我能撸起袖子,直接与其他开发人员一起工作,提供一些代码示例供他们参考,并提供一些密切的支持,团队就能更好地理解模型的抽象概念并加以运用。
First, some of the model’s intent was lost in the handoff. The overall effect of a model can be very sensitive to details (as will be discussed in Parts II and III), and those details don’t always come across in a UML diagram or a general discussion. If I could have rolled up my sleeves and worked with the other developers directly, providing some code to follow as examples, and providing some close support, the team could have taken up the abstractions of the model and run with them.
另一个问题是模型与实现和技术交互过程中反馈的间接性。例如,模型的某些方面在我们的技术平台上效率极低,但几个月后我才完全了解其全部影响。其实只需稍作修改就能解决这个问题,但那时已经无济于事了。开发人员已经能够编写出真正可用的软件——而无需使用模型,模型已被简化为单纯的数据结构,无论它还在何处使用。开发人员矫枉过正,但他们又能怎么办呢?他们再也承受不起被象牙塔里的架构师牵着鼻子走的风险了。
The other problem was the indirectness of feedback from the interaction of the model with the implementation and the technology. For example, certain aspects of the model turned out to be wildly inefficient on our technology platform, but the full implications didn’t trickle back to me for months. Relatively minor changes could have fixed the problem, but by then it didn’t matter. The developers were well on their way to writing software that did work—without the model, which had been reduced to a mere data structure, wherever it was still used at all. The developers had thrown the baby out with the bathwater, but what choice did they have? They could no longer risk being saddled with the dictates of the architect in the ivory tower.
这个项目的初始条件对于一个无需过多干预的建模人员来说,可谓是极其有利的。我对项目中使用的大部分技术都拥有丰富的实践经验。在我角色转变之前,我甚至还领导过一个小型开发团队参与同一个项目,因此我对项目的开发流程和编程环境都非常熟悉。然而,即便如此,由于建模和实现工作是分离的,我仍然难以高效地完成工作。
The initial circumstances of this project were about as favorable to a hands-off modeler as they ever are. I already had extensive hands-on experience with most of the technology used on the project. I had even led a small development team on the same project before my role changed, so I was familiar with the project’s development process and programming environment. Even those factors were not enough to make me effective, given the separation of modeler from implementation.
如果编写代码的人员对模型不承担责任,或者不理解如何将模型应用于实际应用,那么模型就与软件本身毫无关联。如果开发人员没有意识到修改代码会改变模型,那么他们的重构非但不会增强模型,反而会削弱模型。同时,当建模人员脱离实现过程时,他们要么永远无法获得对实现约束的理解,要么很快就会失去这种理解。模型驱动设计的基本约束——模型支持有效的实现并抽象出关键的领域知识——已经缺失了一半,最终得到的模型将不切实际。最后,如果分工阻碍了那种能够传达模型驱动设计编码精髓的协作,那么经验丰富的设计师的知识和技能就无法传递给其他开发人员。
If the people who write the code do not feel responsible for the model, or don’t understand how to make the model work for an application, then the model has nothing to do with the software. If developers don’t realize that changing code changes the model, then their refactoring will weaken the model rather than strengthen it. Meanwhile, when a modeler is separated from the implementation process, he or she never acquires, or quickly loses, a feel for the constraints of implementation. The basic constraint of MODEL-DRIVEN DESIGN—that the model supports an effective implementation and abstracts key domain knowledge—is half-gone, and the resulting models will be impractical. Finally, the knowledge and skills of experienced designers won’t be transferred to other developers if the division of labor prevents the kind of collaboration that conveys the subtleties of coding a MODEL-DRIVEN DESIGN.
对动手建模人员的需求并不意味着团队成员不能有专门的角色。包括极限编程在内的所有敏捷流程都会为团队成员定义角色,其他非正式的专业分工也往往会自然而然地出现。问题在于,在模型驱动设计中,建模和实现这两个相互关联的任务被割裂开来。
The need for HANDS-ON MODELERS does not mean that team members cannot have specialized roles. Every Agile process, including Extreme Programming, defines roles for team members, and other informal specializations tend to emerge naturally. The problem arises from separating two tasks that are coupled in a MODEL-DRIVEN DESIGN, modeling and implementation.
整体设计的有效性对细粒度设计和实现决策的质量和一致性非常敏感。在模型驱动设计中,一部分代码是模型的表达;修改这部分代码就会改变模型。程序员就是建模者,不管别人喜不喜欢。因此,最好在项目设置中就考虑到程序员需要做好建模工作。
The effectiveness of an overall design is very sensitive to the quality and consistency of fine-grained design and implementation decisions. With a MODEL-DRIVEN DESIGN, a portion of the code is an expression of the model; changing that code changes the model. Programmers are modelers, whether anyone likes it or not. So it is better to set up the project so that the programmers do good modeling work.
所以:
Therefore:
任何参与模型开发的技术人员,无论其主要角色是什么,都必须花一些时间接触代码。在项目中,任何负责修改代码的人都必须学会用代码表达模型。每位开发者都必须参与到一定程度的模型讨论中,并与领域专家保持联系。所有以不同方式做出贡献的人员都必须有意识地引导那些接触代码的人员,通过通用语言(UBIQUITOUS LANGUAGE)进行模型理念的动态交流。
Any technical person contributing to the model must spend some time touching the code, whatever primary role he or she plays on the project. Anyone responsible for changing code must learn to express a model through the code. Every developer must be involved in some level of discussion about the model and have contact with domain experts. Those who contribute in different ways must consciously engage those who touch the code in a dynamic exchange of model ideas through the UBIQUITOUS LANGUAGE.
将建模和编程截然分开的做法行不通,但大型项目仍然需要技术领导者来协调高层设计和建模工作,并协助制定最棘手或最关键的决策。第四部分“战略设计”探讨了此类决策,并旨在启发人们思考如何更有效地定义高层技术人员的角色和职责。
The sharp separation of modeling and programming doesn’t work, yet large projects still need technical leaders who coordinate high-level design and modeling and help work out the most difficult or most critical decisions. Part IV, “Strategic Design,” deals with such decisions and should stimulate ideas for more productive ways to define the roles and responsibilities of high-level technical people.
领域驱动设计运用模型来解决应用程序的问题。通过知识梳理,团队将海量混乱的信息提炼成一个实用的模型。模型驱动设计则将模型与实现紧密联系起来。通用语言是连接开发人员、领域专家和软件之间所有信息的渠道。
Domain-driven design puts a model to work to solve problems for an application. Through knowledge crunching, a team distills a torrent of chaotic information into a practical model. A MODEL-DRIVEN DESIGN intimately connects the model and the implementation. The UBIQUITOUS LANGUAGE is the channel for all that information to flow between developers, domain experts, and the software.
最终得到的软件基于对核心领域的深刻理解,提供了丰富的功能。
The result is software that provides rich functionality based on a fundamental understanding of the core domain.
如前所述,模型驱动设计的成功与否取决于详细的设计决策,而这正是接下来几章的主题。
As mentioned, success with MODEL-DRIVEN DESIGN is sensitive to detailed design decisions, which is the subject of the next several chapters.
为了使软件实现清晰简洁,并与模型保持一致,即使面对纷繁复杂的现实,也必须应用建模和设计的最佳实践。本书并非面向对象设计的入门读物,也不提出激进的设计理念。领域驱动设计改变了某些传统观念的侧重点。
To keep a software implementation crisp and in lockstep with a model, in spite of messy realities, you must apply the best practices of modeling and design. This book is not an introduction to object-oriented design, nor does it propose radical design fundamentals. Domain-driven design shifts the emphasis of certain conventional ideas.
某些类型的决策能够确保模型和实现方式保持一致,彼此相辅相成,增强对方的有效性。这种一致性需要关注各个细节。在这个小范围内进行精心设计,能够为开发人员提供一个稳定的平台,以便应用第三部分和第四部分中的建模方法。
Certain kinds of decisions keep the model and implementation aligned with each other, each reinforcing the other’s effectiveness. This alignment requires attention to the details of individual elements. Careful crafting at this small scale gives developers a steady platform from which to apply the modeling approaches of Parts III and IV.
本书的设计风格主要遵循Wirfs-Brock等人于1990年提出的“责任驱动设计”原则,该原则在2003年进行了更新。此外,本书还大量借鉴了Meyer在1988年提出的“契约式设计”理念(尤其是在第三部分)。它与面向对象设计的其他公认最佳实践的总体背景相一致,这些实践在Larman(1998)等著作中有所描述。
The design style in this book largely follows the principle of “responsibility-driven design,” put forward in Wirfs-Brock et al. 1990 and updated in Wirfs-Brock 2003. It also draws heavily (especially in Part III) on the ideas of “design by contract” described in Meyer 1988. It is consistent with the general background of other widely held best practices of object-oriented design, which are described in such books as Larman 1998.
项目在进行过程中,无论遇到大小问题,开发人员都可能发现这些原则似乎不再适用。为了使领域驱动设计流程更具韧性,开发人员需要理解这些众所周知的基本原理如何支持模型驱动设计,以便在不偏离正轨的情况下做出妥协。
As a project hits bumps, large or small, developers may find themselves in situations that make those principles seem inapplicable. To make the domain-driven design process resilient, developers need to understand how the well-known fundamentals support MODEL-DRIVEN DESIGN, so they can compromise without derailing.
接下来的三章内容以“模式语言”(见附录 A)的形式组织,它将展示微妙的模型区别和设计决策如何影响领域驱动的设计过程。
The material in the following three chapters is organized as a “pattern language” (see Appendix A), which will show how subtle model distinctions and design decisions affect the domain-driven design process.
下一页顶部的图表是一张导航图。它展示了本节将要介绍的模式以及它们之间的一些关联方式。
The diagram on the top of the next page is a navigation map. It shows the patterns that will be presented in this section and a few of the ways they relate to each other.
分享这些标准模式能够使设计更有条理,并使团队成员更容易理解彼此的工作。使用标准模式还有助于构建通用语言,所有团队成员都可以使用这种语言来讨论模型和设计决策。
Sharing these standard patterns brings order to the design and makes it easier for team members to understand each other’s work. Using standard patterns also adds to the UBIQUITOUS LANGUAGE, which all team members can use to discuss model and design decisions.
构建一个好的领域模型是一门艺术。但模型各个元素的实际设计和实现可以相对系统化。将领域设计与其他大量考量因素隔离开来。在软件系统中,这将极大地阐明设计与模型之间的联系。根据特定区分定义模型元素可以明确其含义。遵循已验证的元素模式有助于构建易于实施的实用模型。
Developing a good domain model is an art. But the practical design and implementation of a model’s individual elements can be relatively systematic. Isolating the domain design from the mass of other concerns in the software system will greatly clarify the design’s connection to the model. Defining model elements according to certain distinctions sharpens their meanings. Following proven patterns for individual elements helps produce a model that is practical to implement.
模型驱动设计语言导航图
A navigation map of the language of MODEL-DRIVEN DESIGN
只有注重基本原理,精心设计的模型才能化繁为简,最终形成团队可以自信地组合在一起的详细要素。
Elaborate models can cut through complexity only if care is taken with the fundamentals, resulting in detailed elements that the team can confidently combine.
软件中专门解决领域问题的部分通常只占整个软件系统的一小部分,但其重要性却与其规模不成比例。为了更好地理解问题,我们需要能够将模型中的各个元素视为一个系统。我们不能像在夜空中辨认星座一样,被迫从一大堆对象中挑选出它们。我们需要将领域对象与系统的其他功能解耦,这样才能避免将领域概念与仅与软件技术相关的概念混淆,或者在庞大的系统环境中完全忽略领域本身。
The part of the software that specifically solves problems from the domain usually constitutes only a small portion of the entire software system, although its importance is disproportionate to its size. To apply our best thinking, we need to be able to look at the elements of our model and see them as a system. We must not be forced to pick them out of a much larger mix of objects, like trying to identify constellations in the night sky. We need to decouple the domain objects from other functions of the system, so we can avoid confusing the domain concepts with other concepts related only to software technology or losing sight of the domain altogether in the mass of the system.
用于这种隔离的复杂技术已经出现。这虽然是一个老生常谈的领域,但它对于成功应用领域建模原则至关重要,因此必须从领域驱动的角度对其进行简要回顾……
Sophisticated techniques for this isolation have emerged. This is well-trodden ground, but it is so critical to the successful application of domain-modeling principles that it must be reviewed briefly, from a domain-driven point of view. . . .
为了使货运应用程序能够支持用户从城市列表中选择货物目的地这一简单操作,程序代码必须能够完成以下五个步骤:(1) 在屏幕上绘制控件;(2) 查询数据库以获取所有可能的城市;(3) 解析并验证用户输入;(4) 将所选城市与货物关联起来;(5) 将更改提交到数据库。所有这些代码都属于同一个程序,但其中只有一小部分与货运业务相关。
For a shipping application to support the simple user act of selecting a cargo’s destination from a list of cities, there must be program code that (1) draws a widget on the screen, (2) queries the database for all the possible cities, (3) interprets the user’s input and validates it, (4) associates the selected city with the cargo, and (5) commits the change to the database. All of this code is part of the same program, but only a little of it is related to the business of shipping.
软件程序涉及设计和编码,用于执行各种不同的任务。它们接收用户输入、执行业务逻辑、访问数据库、通过网络通信、向用户显示信息等等。因此,每个程序功能所涉及的代码量都可能相当庞大。
Software programs involve design and code to carry out many different kinds of tasks. They accept user input, carry out business logic, access databases, communicate over networks, display information to users, and so on. So the code involved in each program function can be substantial.
在面向对象的程序设计中,用户界面、数据库和其他支持代码通常直接编写在业务对象中。额外的业务逻辑则嵌入在用户界面组件和数据库脚本的行为中。这样做是因为在短期内,这是实现功能最简单的方法。
In an object-oriented program, UI, database, and other support code often gets written directly into the business objects. Additional business logic is embedded in the behavior of UI widgets and database scripts. This happens because it is the easiest way to make things work, in the short run.
当领域相关的代码分散在大量其他代码中时,就很难查看和理解。对用户界面进行的表面改动实际上可能会改变业务逻辑。更改一条业务规则可能需要仔细追踪用户界面代码、数据库代码或其他程序元素。实现连贯的、模型驱动的对象变得不切实际。自动化测试也变得十分困难。由于每个活动都涉及各种技术和逻辑,程序必须保持简洁,否则将难以理解。
When the domain-related code is diffused through such a large amount of other code, it becomes extremely difficult to see and to reason about. Superficial changes to the UI can actually change business logic. To change a business rule may require meticulous tracing of UI code, database code, or other program elements. Implementing coherent, model-driven objects becomes impractical. Automated testing is awkward. With all the technologies and logic involved in each activity, a program must be kept very simple or it becomes impossible to understand.
创建能够处理极其复杂任务的程序需要关注点分离,从而可以分别专注于设计的不同部分。同时,尽管进行了关注点分离,系统内部错综复杂的交互也必须得以维持。
Creating programs that can handle very complex tasks calls for separation of concerns, allowing concentration on different parts of the design in isolation. At the same time, the intricate interactions within the system must be maintained in spite of the separation.
软件系统的划分方式多种多样,但凭借经验和惯例,业界已逐渐趋向于分层架构,尤其是一些相当标准的层级。分层的概念已被广泛应用,以至于大多数开发人员都能直观地理解。文献中有很多关于分层的优秀论述,有时以模式的形式呈现(例如Buschmann 等人,1996 年,第 31-51 页)。其基本原则是,任何层级中的任何元素都只依赖于同一层级中的其他元素,或者依赖于其“下层”的元素。向上通信必须通过某种间接机制,我稍后会对此进行讨论。
There are all sorts of ways a software system might be divided, but through experience and convention, the industry has converged on LAYERED ARCHITECTURES, and specifically a few fairly standard layers. The metaphor of layering is so widely used that it feels intuitive to most developers. Many good discussions of layering are available in the literature, sometimes in the format of a pattern (as in Buschmann et al. 1996, pp. 31–51). The essential principle is that any element of a layer depends only on other elements in the same layer or on elements of the layers “beneath” it. Communication upward must pass through some indirect mechanism, which I’ll discuss a little later.
分层架构的价值在于每一层都专注于计算机程序的特定方面。这种专业化使得每个方面都能实现更统一的设计,也使得这些设计更容易理解。当然,选择能够隔离最重要的统一设计方面的层至关重要。经验和惯例促成了一些共识。尽管存在许多变体,但大多数成功的架构都使用以下四个概念层的某种形式:
The value of layers is that each specializes in a particular aspect of a computer program. This specialization allows more cohesive designs of each aspect, and it makes these designs much easier to interpret. Of course, it is vital to choose layers that isolate the most important cohesive design aspects. Again, experience and convention have led to some convergence. Although there are many variations, most successful architectures use some version of these four conceptual layers:
有些项目不这样做。将用户界面层和应用层明确区分开来。其他一些系统则有多个基础架构层。但正是领域层的关键分离,才使得模型驱动设计成为可能。
Some projects don’t make a sharp distinction between the user interface and application layers. Others have multiple infrastructure layers. But it is the crucial separation of the domain layer that enables MODEL-DRIVEN DESIGN.
所以:
Therefore:
将复杂的程序划分成多个层。在每一层中设计一个内聚性强且仅依赖于下层的代码结构。遵循标准架构模式,以实现与上层的松耦合。将所有与领域模型相关的代码集中在一个层中,并将其与用户界面、应用程序和基础设施代码隔离。这样,领域对象就无需负责自我展示、自我存储、管理应用程序任务等,从而可以专注于表达……领域模型。这使得模型能够不断发展,变得足够丰富和清晰,从而捕捉关键的业务知识并加以应用。
Partition a complex program into layers. Develop a design within each layer that is cohesive and that depends only on the layers below. Follow standard architectural patterns to provide loose coupling to the layers above. Concentrate all the code related to the domain model in one layer and isolate it from the user interface, application, and infrastructure code. The domain objects, free of the responsibility of displaying themselves, storing themselves, managing application tasks, and so forth, can be focused on expressing the domain model. This allows a model to evolve to be rich enough and clear enough to capture essential business knowledge and put it to work.
将领域层与基础设施层和用户界面层分离,可以显著简化每一层的设计。隔离的层维护成本更低,因为它们的演进速度不同,对不同需求的响应也不同。这种分离还有助于分布式系统的部署,允许将不同的层灵活地部署在不同的服务器或客户端上,从而最大限度地减少通信开销并提高性能(Fowler 1996)。
Separating the domain layer from the infrastructure and user interface layers allows a much cleaner design of each layer. Isolated layers are much less expensive to maintain, because they tend to evolve at different rates and respond to different needs. The separation also helps with deployment in a distributed system, by allowing different layers to be placed flexibly in different servers or clients, in order to minimize communication overhead and improve performance (Fowler 1996).
该应用程序提供多种银行账户管理功能。其中一项功能是资金转账,用户可以输入或选择两个账号和转账金额,然后发起转账。
An application provides various capabilities for maintaining bank accounts. One feature is funds transfer, in which the user enters or chooses two account numbers and an amount of money and then initiates a transfer.
为了使示例易于理解,我省略了一些重要的技术特性,尤其是安全性。领域设计也进行了过度简化。(实际的复杂性只会增加分层架构的必要性。)此外,这里所隐含的特定基础设施旨在简单明了,以使示例清晰易懂——它并非推荐的设计方案。其余功能的职责将如图4.1所示进行分层。
To make this example manageable, I’ve omitted major technical features, most notably security. The domain design is also oversimplified. (Realistic complexity would only increase the need for layered architecture.) Furthermore, the particular infrastructure implied here is meant to be simple and obvious to make the example clear—it is not a suggested design. The responsibilities of the remaining functionality would be layered as shown in Figure 4.1.
图 4.1. 对象履行与其所在层级相符的职责,并且与所在层级中的其他对象更加紧密地联系在一起。
Figure 4.1. Objects carry out responsibilities consistent with their layer and are more coupled to other objects in their layer.
请注意,领域层(而不是应用层)负责基本业务规则——在本例中,规则是“每个贷项都有一个匹配的借项”。
Note that the domain layer, not the application layer, is responsible for fundamental business rules—in this case, the rule is “Every credit has a matching debit.”
该应用程序对转账请求的来源不做任何假设。程序可能包含一个用户界面,其中包含用于输入账号和金额的字段以及用于执行命令的按钮。但是,该用户界面可以被 XML 格式的汇款请求所取代,而不会影响应用程序层或任何底层。这种解耦之所以重要,并非因为项目经常需要用汇款请求替换用户界面,而是因为清晰的职责分离能够使每一层的设计都易于理解和维护。
The application also makes no assumptions about the source of the transfer request. The program presumably includes a UI with entry fields for account numbers and amounts and with buttons for commands. But that user interface could be replaced by a wire request in XML without affecting the application layer or any of the lower layers. This decoupling is important not because projects frequently need to replace user interfaces with wire requests but because a clean separation of concerns keeps the design of each layer easy to understand and maintain.
事实上,图 4.1 这本身就略微说明了不隔离领域层的问题。由于从请求到事务控制的所有内容都必须包含在内,领域层不得不被简化,以保持整体交互足够简单易懂。如果我们专注于设计隔离的领域层,我们就能在页面上和脑海中腾出空间,构建一个更好地表示领域规则的模型,例如包含账簿、贷记和借记对象,或者货币交易对象。
In fact, Figure 4.1 itself mildly illustrates the problem of not isolating the domain. Because everything from the request to transaction control had to be included, the domain layer had to be dumbed down to keep the overall interaction simple enough to follow. If we were focused on the design of the isolated domain layer, we would have space on the page and in our heads for a model that better represented the domain’s rules, perhaps including ledgers, credit and debit objects, or monetary transaction objects.
到目前为止,讨论主要集中在层级分离以及这种划分如何改进程序各个方面的设计,尤其是领域层。但当然,各层之间必须相互连接。如何在不损失层级分离优势的前提下实现这一点,正是许多模式背后的驱动力。
So far the discussion has focused on the separation of layers and the way in which that partitioning improves the design of each aspect of the program, particularly the domain layer. But of course, the layers have to be connected. To do this without losing the benefit of the separation is the motivation behind a number of patterns.
各层之间应保持松耦合,设计依赖关系仅存在于一个方向上。上层可以使用或操作下层。可以直接通过调用底层元素的公共接口、持有对它们的引用(至少是暂时的)以及通常使用常规的交互方式来访问它们。但是,当底层对象需要向上层通信(而不仅仅是回答直接查询)时,我们需要另一种机制,借鉴用于关联层的架构模式,例如回调或观察者(Gamma 等人,1995)。
Layers are meant to be loosely coupled, with design dependencies in only one direction. Upper layers can use or manipulate elements of lower ones straightforwardly by calling their public interfaces, holding references to them (at least temporarily), and generally using conventional means of interaction. But when an object of a lower level needs to communicate upward (beyond answering a direct query), we need another mechanism, drawing on architectural patterns for relating layers such as callbacks or OBSERVERS (Gamma et al. 1995).
将用户界面 (UI) 与应用层和领域层连接起来的模式鼻祖是模型-视图-控制器(MVC) 模式。它于 20 世纪 70 年代在 Smalltalk 世界中率先提出,并启发了之后许多 UI 架构。Fowler (2003) 讨论了这种模式及其几个有用的变体。Larman (1998)在模型-视图分离模式中探讨了这些问题,而他的应用协调器 (APPLICATION COORDINATOR)则是连接应用层的一种方法。
The grandfather of patterns for connecting the UI to the application and domain layers is MODEL-VIEW-CONTROLLER (MVC). It was pioneered in the Smalltalk world back in the 1970s and has inspired many of the UI architectures that followed. Fowler (2003) discusses this pattern and several useful variations on the theme. Larman (1998) explores these concerns in the MODEL-VIEW SEPARATION PATTERN, and his APPLICATION COORDINATOR is one approach to connecting the application layer.
还有其他连接用户界面和应用程序的方式。就我们的目的而言,只要能保持领域层的隔离,所有方法都可以接受,这样就可以在设计领域对象时无需同时考虑可能与之交互的用户界面。
There are other styles of connecting the UI and the application. For our purposes, all approaches are fine as long as they maintain the isolation of the domain layer, allowing domain objects to be designed without simultaneously thinking about the user interface that might interact with them.
基础设施层通常不会主动发起领域层的操作。由于它位于领域层“之下”,因此不应了解其所服务的领域的具体情况。实际上,这类技术能力通常以服务(SERVICE)的形式提供。例如,如果一个应用程序需要发送电子邮件,那么消息发送接口可以位于基础设施层,而应用层组件可以请求发送该消息。这种解耦提供了额外的灵活性。消息发送接口可以连接到电子邮件发送器、传真发送器或任何其他可用的服务。但其主要优势在于简化了应用层,使其专注于自身的核心任务:知道何时发送消息,而无需考虑如何发送。
The infrastructure layer usually does not initiate action in the domain layer. Being “below” the domain layer, it should have no specific knowledge of the domain it is serving. Indeed, such technical capabilities are most often offered as SERVICES. For example, if an application needs to send an e-mail, some message-sending interface can be located in the infrastructure layer and the application layer elements can request the transmission of the message. This decoupling gives some extra versatility. The message-sending interface might be connected to an e-mail sender, a fax sender, or whatever else is available. But the main benefit is simplifying the application layer, keeping it narrowly focused on its job: knowing when to send a message, but not burdened with how.
应用层和领域层调用基础设施层提供的服务。当服务的范围选择得当且接口设计合理时,调用者可以保持松耦合,而不会被服务接口封装的复杂行为所困扰。
The application and domain layers call on the SERVICES provided by the infrastructure layer. When the scope of a SERVICE has been well chosen and its interface well designed, the caller can remain loosely coupled and uncomplicated by the elaborate behavior the SERVICE interface encapsulates.
但并非所有基础设施都以可从高层调用的服务形式存在。一些技术组件旨在直接支持其他层的基本功能(例如,为所有领域对象提供抽象基类),并提供它们之间相互关联的机制(例如,MVC 等架构的实现)。这种“架构框架”对程序其他部分的设计有着更为深远的影响。
But not all infrastructure comes in the form of SERVICES callable from the higher layers. Some technical components are designed to directly support the basic functions of other layers (such as providing an abstract base class for all domain objects) and provide the mechanisms for them to relate (such as implementations of MVC and the like). Such an “architectural framework” has much more impact on the design of the other parts of the program.
当基础设施以服务(SERVICE)的形式提供,并通过接口调用时,分层架构的工作原理以及如何保持各层之间的松耦合都相当直观。但某些技术问题需要更具侵入性的基础设施形式。集成多种基础设施需求的框架通常要求其他层以非常特殊的方式实现,例如作为框架类的子类或使用结构化的方法签名。(子类位于比父类更高的层级似乎有悖常理,但请记住,哪个类对另一个类的了解更多。)最佳的架构框架能够解决复杂的技术问题,同时让领域开发人员专注于表达模型。但框架也很容易成为阻碍,要么做出过多的假设限制了领域设计选择,要么使实现过于繁重,从而减慢开发速度。
When infrastructure is provided in the form of SERVICES called on through interfaces, it is fairly intuitive how the layering works and how to keep the layers loosely coupled. But some technical problems call for more intrusive forms of infrastructure. Frameworks that integrate many infrastructure needs often require the other layers to be implemented in very particular ways, for example as a subclass of a framework class or with structured method signatures. (It may seem counterintuitive for a subclass to be in a layer higher than that of the parent class, but keep in mind which class reflects more knowledge of the other.) The best architectural frameworks solve complex technical problems while allowing the domain developer to concentrate on expressing a model. But frameworks can easily get in the way, either by making too many assumptions that constrain domain design choices or by making the implementation so heavyweight that development slows down.
通常需要某种架构框架(尽管有时团队选择的框架并不适合他们)。应用框架时,团队需要专注于目标:构建一个能够表达领域模型并用于解决重要问题的实现。团队必须寻找利用框架实现这些目标的方法,即使这意味着无法使用框架的所有功能。例如,早期的 J2EE 应用程序通常将所有领域对象都实现为“实体 bean”。这种方法既降低了性能,也拖慢了开发速度。而目前的最佳实践是将 J2EE 框架用于粒度更大的对象,并使用通用 Java 对象来实现大部分业务逻辑。通过有选择地应用框架来解决难题,而不是寻求一刀切的解决方案,可以避免框架的许多缺点。明智地应用框架中最有价值的部分至关重要。框架特性降低了实现与框架之间的耦合度,从而为后续的设计决策提供了更大的灵活性。更重要的是,鉴于当前许多框架的使用都非常复杂,这种极简主义有助于保持业务对象的可读性和表达力。
Some form of architectural framework usually is needed (though sometimes teams choose frameworks that don’t serve them well). When applying a framework, the team needs to focus on its goal: building an implementation that expresses a domain model and uses it to solve important problems. The team must seek ways of employing the framework to those ends, even if it means not using all of the framework’s features. For example, early J2EE applications often implemented all domain objects as “entity beans.” This approach bogged down both performance and the pace of development. Instead, current best practice is to use the J2EE framework for larger grain objects, implementing most business logic with generic Java objects. A lot of the downside of frameworks can be avoided by applying them selectively to solve difficult problems without looking for a one-size-fits-all solution. Judiciously applying only the most valuable of framework features reduces the coupling of the implementation and the framework, allowing more flexibility in later design decisions. More important, given how very complicated many of the current frameworks are to use, this minimalism helps keep the business objects readable and expressive.
架构框架和其他工具将不断发展演进。新的框架将自动化或预制应用程序越来越多的技术方面。如果运用得当,应用程序开发人员将能够把更多精力集中在核心业务问题的建模上,从而显著提高生产力和质量。但与此同时,我们必须警惕对技术解决方案的过度追求;过于复杂的框架也可能束缚应用程序开发人员的思维。
Architectural frameworks and other tools will continue to evolve. Newer frameworks will automate or prefabricate more and more of the technical aspects of an application. If this is done right, application developers will increasingly concentrate their time on modeling the core business problems, greatly improving productivity and quality. But as we move in this direction, we must guard against our enthusiasm for technical solutions; elaborate frameworks can also straitjacket application developers.
分层架构如今被广泛应用于大多数系统中,并采用不同的分层方案。许多开发风格也能从分层中获益。然而,领域驱动设计仅需存在一个特定的层。
LAYERED ARCHITECTURE is used in most systems today, under various layering schemes. Many styles of development can also benefit from layering. However, domain-driven design requires only one particular layer to exist.
领域模型是一组概念。“领域层”是该模型及其所有直接相关的设计元素的体现。业务逻辑的设计和实现构成了领域层。在模型驱动设计中,领域层的软件结构反映了模型概念。
The domain model is a set of concepts. The “domain layer” is the manifestation of that model and all directly related design elements. The design and implementation of business logic constitute the domain layer. In a MODEL-DRIVEN DESIGN, the software constructs of the domain layer mirror the model concepts.
当领域逻辑与其他程序功能混杂在一起时,实现这种对应关系是不切实际的。隔离领域实现是领域驱动设计的先决条件。
It is not practical to achieve that correspondence when the domain logic is mixed with other concerns of the program. Isolating the domain implementation is a prerequisite for domain-driven design.
……以上概括了被广泛接受的面向对象应用程序的分层架构模式。但是,这种将用户界面、应用程序和领域分离的尝试屡屡失败,以至于其否定本身就值得单独探讨。
. . . That sums up the widely accepted LAYERED ARCHITECTURE pattern for object applications. But this separation of UI, application, and domain is so often attempted and so seldom accomplished that its negation deserves a discussion in its own right.
许多软件项目确实采用(并且应该继续采用)一种远没有那么复杂的设计方法,我称之为SMART UI。但SMART UI 是一条截然不同的、互斥的岔路,与领域驱动设计方法不兼容。如果选择了这条路,本书的大部分内容就不适用了。我感兴趣的是SMART UI 不适用的情况,所以我才半开玩笑地称它为“反模式”。在这里讨论它提供了一个有用的对比,并有助于阐明本书其余部分所采用的更为复杂的方法的合理性。
Many software projects do take and should continue to take a much less sophisticated design approach that I call the SMART UI. But SMART UI is an alternate, mutually exclusive fork in the road, incompatible with the approach of domain-driven design. If that road is taken, most of what is in this book is not applicable. My interest is in the situations where the SMART UI does not apply, which is why I call it, with tongue in cheek, an “anti-pattern.” Discussing it here provides a useful contrast and will help clarify the circumstances that justify the more difficult path taken in the rest of the book.
项目需要实现简单的功能,主要侧重于数据录入和显示,业务规则较少。团队成员并非都是高级对象建模师。
A project needs to deliver simple functionality, dominated by data entry and display, with few business rules. Staff is not composed of advanced object modelers.
如果一个经验不足的团队决定尝试采用分层架构的模型驱动设计,并开展一个简单的项目,那么他们将面临一段艰难的学习过程。团队成员必须掌握复杂的新技术,并在学习对象建模的过程中磕磕绊绊(即使有本书的帮助,这仍然充满挑战!)。管理基础设施和分层架构的额外开销会使原本简单的任务耗时更长。简单的项目通常时间紧迫,预期也不高。在团队完成分配的任务之前,更遑论展现其方法的潜力,项目很可能就已经被取消了。
If an unsophisticated team with a simple project decides to try a MODEL-DRIVEN DESIGN with LAYERED ARCHITECTURE, it will face a difficult learning curve. Team members will have to master complex new technologies and stumble through the process of learning object modeling (which is challenging, even with the help of this book!). The overhead of managing infrastructure and layers makes very simple tasks take longer. Simple projects come with short time lines and modest expectations. Long before the team completes the assigned task, much less demonstrates the exciting possibilities of its approach, the project will have been canceled.
即使给予团队更多时间,如果没有专家指导,团队成员也很难掌握这些技术。最终,即便他们克服了这些挑战,也只会开发出一个简单的系统。他们从未要求过强大的功能。
Even if the team is given more time, the team members are likely to fail to master the techniques without expert help. And in the end, if they do surmount these challenges, they will have produced a simple system. Rich capabilities were never requested.
经验更丰富的团队不会面临同样的权衡取舍。经验丰富的开发人员可以降低学习曲线,缩短管理各层所需的时间。领域驱动设计最终会带来回报。最适合雄心勃勃的项目,而且确实需要很强的技能。并非所有项目都雄心勃勃,也并非所有项目团队都能掌握这些技能。
A more experienced team would not face the same trade-offs. Seasoned developers could flatten the learning curve and compress the time needed to manage the layers. Domain-driven design pays off best for ambitious projects, and it does require strong skills. Not all projects are ambitious. Not all project teams can muster those skills.
因此,当情况需要时:
Therefore, when circumstances warrant:
将所有业务逻辑都放在用户界面中。将应用程序拆分成多个小功能,并将它们实现为独立的用户界面,同时将业务规则嵌入其中。使用关系型数据库作为共享数据存储库。使用最先进的自动化 UI 构建和可视化编程工具。
Put all the business logic into the user interface. Chop the application into small functions and implement them as separate user interfaces, embedding the business rules into them. Use a relational database as a shared repository of the data. Use the most automated UI building and visual programming tools available.
异端邪说!正统观点(本书其他章节也反复强调)认为领域和用户界面应该分离。事实上,如果不进行这种分离,本书后续讨论的任何方法都难以应用,因此,在领域驱动设计的语境下, SMART UI 可以被视为一种“反模式”。然而,在其他一些情况下,它却是一种合理的模式。诚然,SMART UI 有其优势,并且在某些情况下效果最佳——这也部分解释了它为何如此普遍。在此探讨 SMART UI 有助于我们理解为什么需要将应用程序与领域分离,以及更重要的是,在哪些情况下我们可能不希望这样做。
Heresy! The gospel (as advocated everywhere, including elsewhere in this book) is that domain and UI should be separate. In fact, it is difficult to apply any of the methods discussed later in this book without that separation, and so this SMART UI can be considered an “anti-pattern” in the context of domain-driven design. Yet it is a legitimate pattern in some other contexts. In truth, there are advantages to the SMART UI, and there are situations where it works best—which partially accounts for why it is so common. Considering it here helps us understand why we need to separate application from domain and, importantly, when we might not want to.
优势
Advantages
• 对于简单的应用,生产效率高且见效快。
• Productivity is high and immediate for simple applications.
• 能力较弱的开发人员只需少量培训即可采用这种方式工作。
• Less capable developers can work this way with little training.
• 即使需求分析存在缺陷,也可以通过向用户发布原型,然后快速更改产品以满足他们的要求来克服。
• Even deficiencies in requirements analysis can be overcome by releasing a prototype to users and then quickly changing the product to fit their requests.
• 各个应用程序彼此解耦,因此可以相对准确地规划小型模块的交付计划。向系统添加额外的简单功能也很容易。
• Applications are decoupled from each other, so that delivery schedules of small modules can be planned relatively accurately. Expanding the system with additional, simple behavior can be easy.
• 关系型数据库运行良好,并可在数据层面提供集成。
• Relational databases work well and provide integration at the data level.
• 4GL 工具运行良好。
• 4GL tools work well.
• 当应用程序移交时,维护程序员可以快速重做他们无法解决的部分,因为更改的影响应该仅限于每个特定的用户界面。
• When applications are handed off, maintenance programmers will be able to quickly redo portions they can’t figure out, because the effects of the changes should be localized to each particular UI.
• 除了通过数据库之外,应用程序的集成非常困难。
• Integration of applications is difficult except through the database.
• 行为无法重用,业务问题也没有被抽象化。业务规则必须在每个应用操作中重复编写。
• There is no reuse of behavior and no abstraction of the business problem. Business rules have to be duplicated in each operation to which they apply.
• 快速原型设计和迭代会达到一个自然的极限,因为缺乏抽象限制了重构选项。
• Rapid prototyping and iteration reach a natural limit because the lack of abstraction limits refactoring options.
• 复杂性会迅速将你淹没,因此发展路径只能是开发更多简单的应用程序。没有优雅的途径来实现更丰富的功能。
• Complexity buries you quickly, so the growth path is strictly toward additional simple applications. There is no graceful path to richer behavior.
如果团队有意识地运用这种模式,就能避免其他方法带来的大量额外开销。一个常见的错误是采用过于复杂的设计方案,而团队却没有决心将其贯彻到底。另一个常见的、代价高昂的错误是,为一个并不需要的项目构建复杂的架构并使用功能强大的工具。
If this pattern is applied consciously, a team can avoid taking on a great deal of overhead required by other approaches. It is a common mistake to undertake a sophisticated design approach that the team isn’t committed to carrying all the way through. Another common, costly mistake is to build a complex infrastructure and use industrial-strength tools for a project that doesn’t need them.
对于这些应用来说,大多数灵活的语言(例如 Java)都过于复杂,而且成本高昂。使用类似 4GL 的工具才是最佳选择。
Most flexible languages (such as Java) are overkill for these applications and will cost dearly. A 4GL-style tool is the way to go.
记住,这种模式的后果之一是,除非替换整个应用程序,否则你无法迁移到其他设计方法。仅仅使用像 Java 这样的通用编程语言并不能让你以后轻易放弃SMART UI,所以如果你选择了这条路,就应该选择与之配套的开发工具。不要给自己留后路。仅仅使用灵活的语言并不能创建一个灵活的系统,反而很可能会创建一个成本高昂的系统。
Remember, one of the consequences of this pattern is that you can’t migrate to another design approach except by replacing entire applications. Just using a general-purpose language such as Java won’t really put you in a position to later abandon the SMART UI, so if you’ve chosen that path, you should choose development tools geared to it. Don’t bother hedging your bet. Just using a flexible language doesn’t create a flexible system, but it may well produce an expensive one.
同样,致力于模型驱动设计的团队需要从一开始就按照模型驱动的方式进行设计。当然,即使是经验丰富、雄心勃勃的项目团队也必须从简单的功能入手,通过不断迭代逐步完善。但最初的几个尝试步骤必须是模型驱动的,并采用独立的领域层,否则项目很可能最终只能停留在SMART UI 的阶段。
By the same token, a team committed to a MODEL-DRIVEN DESIGN needs to design that way from the outset. Of course, even experienced project teams with big ambitions have to start with simple functionality and work their way up through successive iterations. But those first tentative steps will be MODEL-DRIVEN with an isolated domain layer, or the project will most likely be stuck with a SMART UI.
SMART 讨论 UI 只是为了阐明为什么以及何时需要分层架构之类的模式来隔离领域层。
The SMART UI is discussed only to clarify why and when a pattern such as LAYERED ARCHITECTURE is needed in order to isolate a domain layer.
SMART UI 和分层架构之间还有其他解决方案。例如,Fowler (2003) 描述了事务脚本,它将用户界面与应用程序分离,但没有提供对象模型。关键在于:如果架构能够以某种方式隔离领域相关代码,从而实现内聚性强且与系统其他部分松耦合的领域设计,那么该架构很可能支持领域驱动设计。
There are other solutions in between SMART UI and LAYERED ARCHITECTURE. For example, Fowler (2003) describes the TRANSACTION SCRIPT, which separates UI from application but does not provide for an object model. The bottom line is this: If the architecture isolates the domain-related code in a way that allows a cohesive domain design loosely coupled to the rest of the system, then that architecture can probably support domain-driven design.
其他开发风格也有其用武之地,但你必须接受它们在复杂性和灵活性方面的不同限制。在某些情况下,未能将领域设计解耦可能会造成灾难性的后果。如果你有一个复杂的应用程序,并且决心采用模型驱动设计,那就咬紧牙关,聘请必要的专家,并避免使用SMART UI 。
Other development styles have their place, but you must accept varying limits on complexity and flexibility. Failing to decouple the domain design can really be disastrous in certain settings. If you have a complex application and are committing to MODEL-DRIVEN DESIGN, bite the bullet, get the necessary experts, and avoid the SMART UI.
遗憾的是,除了基础设施和用户界面之外,还有其他因素会破坏您精心构建的领域模型。您必须处理那些尚未完全集成到模型中的其他领域组件。您还必须应对使用同一领域不同模型的其他开发团队。这些因素以及其他因素都可能模糊您的模型,使其失去效用。第 14 章“维护模型完整性”探讨了这一主题,并介绍了诸如“有界上下文”和“防腐层”等模式。一个过于复杂的领域模型本身就可能变得难以管理。第 15 章“提炼”讨论了如何在领域层内进行区分,从而将领域的核心概念从无关细节中剥离出来。
Unfortunately, there are influences other than infrastructure and user interfaces that can corrupt your delicate domain model. You must deal with other domain components that are not fully integrated into your model. You have to cope with other development teams who use different models of the same domain. These and other factors can blur your model and rob it of its utility. Chapter 14, “Maintaining Model Integrity,” deals with this topic, introducing such patterns as BOUNDED CONTEXT and ANTICORRUPTION LAYER. A really complicated domain model can become unwieldy all by itself. Chapter 15, “Distillation,” discusses how to make distinctions within the domain layer that can unencumber the essential concepts of the domain from peripheral detail.
但这些都是后话。接下来,我们将深入探讨如何协同演化一个有效的领域模型和一个富有表现力的实现。毕竟,隔离领域模型最大的好处就是可以排除其他干扰因素,让我们真正专注于领域设计。
But all that comes later. Next, we’ll look at the nuts and bolts of co-evolving an effective domain model and an expressive implementation. After all, the best part of isolating the domain is getting all that other stuff out of the way so that we can really focus on the domain design.
要在实现过程中做出妥协,同时又不丧失模型驱动设计的优势,就需要重新构建其基本概念。模型与实现的衔接必须在细节层面进行。本章重点关注这些具体的模型元素,使它们能够支持后续章节的各项活动。
To compromise in implementation without losing the punch of a MODEL-DRIVEN DESIGN requires a reframing of the basics. Connecting model and implementation has to be done at the detail level. This chapter focuses on those individual model elements, getting them in shape to support the activities in later chapters.
本次讨论将从设计和简化关联关系入手。对象之间的关联关系构思和绘制都很简单,但实现起来却可能困难重重。关联关系充分说明了详细的实现决策对于模型驱动设计能否成功至关重要。
This discussion will start with the issues of designing and streamlining associations. Associations between objects are simple to conceive and to draw, but implementing them is a potential quagmire. Associations illustrate how crucial detailed implementation decisions are to the viability of a MODEL-DRIVEN DESIGN.
接下来,我们将转向对象本身,但继续仔细审视详细的模型选择和实现问题之间的关系,我们将重点区分表达模型的三种模型元素模式:实体、值对象和服务。
Turning to the objects themselves, but continuing to scrutinize the relationship between detailed model choices and implementation concerns, we’ll focus on making distinctions among the three patterns of model elements that express the model: ENTITIES, VALUE OBJECTS, and SERVICES.
表面上看,定义能够捕捉领域概念的对象似乎非常直观,但实际上,在意义的细微差别中却隐藏着严峻的挑战。一些区分已经出现,它们阐明了模型元素的含义,并与一系列用于构建特定类型对象的设计实践相联系。
Defining objects that capture concepts of the domain seems very intuitive on the surface, but serious challenges are lurking in the shades of meaning. Certain distinctions have emerged that clarify the meaning of model elements and tie into a body of design practices for carving out specific kinds of objects.
对象代表的是具有连续性和同一性的事物吗?是能够追踪其在不同状态甚至不同实现方式下的本质?还是仅仅描述其他事物状态的属性?这就是实体和值对象之间的基本区别。明确定义遵循某种模式的对象,可以减少对象的歧义,并为稳健设计的具体选择铺平道路。
Does an object represent something with continuity and identity—something that is tracked through different states or even across different implementations? Or is it an attribute that describes the state of something else? This is the basic distinction between an ENTITY and a VALUE OBJECT. Defining objects that clearly follow one pattern or the other makes the objects less ambiguous and lays out the path toward specific choices for robust design.
领域中有些方面更适合用动作或操作来表达,而不是用对象来表达。虽然这与面向对象建模的传统略有不同,但通常最好将这些方面表达为服务(SERVICE),而不是将操作的责任强加给某个实体(ENTITY)或值对象(VALUE OBJECT)。服务是指应客户请求而执行的操作。在软件的技术层面,存在许多服务。当对某些活动进行建模时,如果这些活动对应于软件必须执行的操作,但又不对应于状态,那么服务也会出现在领域中。
Then there are those aspects of the domain that are more clearly expressed as actions or operations, rather than as objects. Although it is a slight departure from object-oriented modeling tradition, it is often best to express these as SERVICES, rather than forcing responsibility for an operation onto some ENTITY or VALUE OBJECT. A SERVICE is something that is done for a client on request. In the technical layers of the software, there are many SERVICES. They emerge in the domain also, when some activity is modeled that corresponds to something the software must do, but does not correspond with state.
在某些情况下,对象模型的纯粹性不可避免地需要做出妥协,例如在关系数据库中存储数据。本章将阐述一些指导原则,帮助您在被迫面对这些棘手的现实时保持正确的方向。
There are inevitable situations in which the purity of the object model must be compromised, such as for storage in a relational database. This chapter will lay out some guidelines for staying on course when you are forced to deal with these messy realities.
最后,对模块的讨论将进一步阐明,每个设计决策都应基于对领域的深刻理解。高内聚和低耦合的概念通常被视为技术指标,但实际上也可以应用于概念本身。在模型驱动设计中,模块是模型的一部分,它们应该反映领域中的概念。
Finally, a discussion of MODULES will drive home the point that every design decision should be motivated by some insight into the domain. The ideas of high cohesion and low coupling, often thought of as technical metrics, can be applied to the concepts themselves. In a MODEL-DRIVEN DESIGN, MODULES are part of the model, and they should reflect concepts in the domain.
本章将所有这些构成要素汇集在一起,它们在软件中体现了模型。这些理念是传统的,由此产生的建模和设计偏差也已被前人论述过。但将它们置于本文的框架下,将有助于开发人员创建详细的组件,从而在处理更宏大的模型和设计问题时,更好地满足领域驱动设计的优先事项。此外,对这些基本原则的理解也将帮助开发人员在不可避免的妥协中保持方向。
This chapter brings together all of these building blocks, which embody the model in software. These ideas are conventional, and the modeling and design biases that follow from them have been written about before. But framing them in this context will help developers create detailed components that will serve the priorities of domain-driven design when tackling the larger model and design issues. Also, a sense of the basic principles will help developers stay on course through the inevitable compromises.
建模与实现之间的交互,尤其是在对象之间存在关联的情况下,会变得特别棘手。
The interaction between modeling and implementation is particularly tricky with the associations between objects.
对于模型中的每个可遍历关联,软件中都有一个具有相同属性的机制。
For every traversable association in the model, there is a mechanism in the software with the same properties.
一个展现客户与销售代表之间关联的模型对应着两层含义。一方面,它抽象化了开发者认为两个真实人物之间存在的关联关系。另一方面,它对应于两个Java对象之间的指针关系,或者数据库查询的封装,或者某种类似的实现。
A model that shows an association between a customer and a sales representative corresponds to two things. On one hand, it abstracts a relationship developers deemed relevant between two real people. On the other hand, it corresponds to an object pointer between two Java objects, or an encapsulation of a database lookup, or some comparable implementation.
例如,一对多关联可以实现为实例变量中的集合。但设计并非总是如此直接。也可能不存在集合;访问器方法会查询数据库以查找相应的记录,并基于这些记录实例化对象。这两种设计都反映了相同的模型。设计必须指定一种特定的遍历机制,其行为与模型中的关联保持一致。
For example, a one-to-many association might be implemented as a collection in an instance variable. But the design is not necessarily so direct. There may be no collection; an accessor method may query a database to find the appropriate records and instantiate objects based on them. Both of these designs would reflect the same model. The design has to specify a particular traversal mechanism whose behavior is consistent with the association in the model.
现实生活中存在大量多对多关联,其中许多关联本身就是双向的。在头脑风暴和探索领域的过程中,早期模型也往往如此。但这些泛化的关联会使模型的实现和维护变得复杂。此外,它们也几乎没有传达关系的本质。
In real life, there are lots of many-to-many associations, and a great number are naturally bidirectional. The same tends to be true of early forms of a model as we brainstorm and explore the domain. But these general associations complicate implementation and maintenance. Furthermore, they communicate very little about the nature of the relationship.
至少有三种方法可以使关联关系更易于处理。
There are at least three ways of making associations more tractable.
1.设定遍历方向
1. Imposing a traversal direction
2.添加限定词,有效减少重复性
2. Adding a qualifier, effectively reducing multiplicity
3.消除不必要的关联
3. Eliminating nonessential associations
尽可能地约束关系至关重要。双向关联意味着两个对象只能一起理解。当应用需求不需要双向遍历时,添加一个遍历方向可以降低相互依赖性并简化设计。理解领域知识可能会揭示出一种自然的方向偏好。
It is important to constrain relationships as much as possible. A bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design. Understanding the domain may reveal a natural directional bias.
美国和许多其他国家一样,有过许多总统。这是一种双向的、一对多的关系。然而,我们很少会先提到“乔治·华盛顿”这个名字,然后问:“他是哪个国家的总统?” 从实际角度来看,我们可以将这种关系简化为单向关联,即从国家指向总统。这种简化实际上反映了我们对……的深刻理解。它不仅扩展了领域,还实现了更实用的设计。它体现了这样一种理解:关联的一个方向远比另一个方向更有意义和重要。它使“人”类独立于远不那么重要的“总统”概念。
The United States has had many presidents, as have many other countries. This is a bidirectional, one-to-many relationship. Yet we seldom would start out with the name “George Washington” and ask, “Of which country was he president?” Pragmatically, we can reduce the relationship to a unidirectional association, traversable from country to president. This refinement actually reflects insight into the domain, as well as making a more practical design. It captures the understanding that one direction of the association is much more meaningful and important than the other. It keeps the “Person” class independent of the far less fundamental concept of “President.”
图 5.1. 一些遍历方向反映了该领域的自然偏差。
Figure 5.1. Some traversal directions reflect a natural bias in the domain.
很多时候,更深入的理解会带来一种“限定性”的关系。深入研究历任总统,我们会发现(或许内战时期除外),一个国家在同一时期只能有一位总统。这种限定性将总统数量减少到一对一,并将一条重要的规则明确地嵌入到模型中。1790 年的美国总统是谁?乔治·华盛顿。
Very often, deeper understanding leads to a “qualified” relationship. Looking deeper into presidents, we realize that (except in a civil war, perhaps) a country has only one president at a time. This qualifier reduces the multiplicity to one-to-one, and explicitly embeds an important rule into the model. Who was president of the United States in 1790? George Washington.
图 5.2. 约束关联传递更多知识,是更实用的设计。
Figure 5.2. Constrained associations communicate more knowledge and are more practical designs.
限制多对多关联的遍历方向,可以有效地将其实现简化为一对多——一种更容易的设计。
Constraining the traversal direction of a many-to-many association effectively reduces its implementation to one-to-many—a much easier design.
始终如一地以反映领域特性的方式来约束关联,不仅能使这些关联更具沟通性且更易于实现,还能赋予剩余的双向关联以意义。当关系的双向性是领域的语义特征,且是应用程序功能所必需的,那么保留双向遍历关系就能体现这一点。
Consistently constraining associations in ways that reflect the bias of the domain not only makes those associations more communicative and simpler to implement, it also gives significance to the remaining bidirectional associations. When the bidirectionality of a relationship is a semantic characteristic of the domain, when it’s needed for application functionality, the retention of both traversal directions conveys that.
当然,如果关联对于当前工作或模型对象的基本含义并非必不可少,那么最终的简化方法是完全消除关联。
Of course, the ultimate simplification is to eliminate an association altogether, if it is not essential to the job at hand or the fundamental meaning of the model objects.
图 5.3
Figure 5.3
该模型中经纪账户的一种Java实现方式是:
One Java implementation of Brokerage Account in this model would be
public class BrokerageAccount {
String accountNumber;
Customer customer;
Set investments;
// Constructors, etc. omitted
public Customer getCustomer() {
return customer;
}
public Set getInvestments() {
return investments;
}
}
但是,如果我们需要从关系数据库中获取数据,那么另一种同样符合模型要求的实现方式如下:
But if we need to fetch the data from a relational database, another implementation, equally consistent with the model, would be the following:
表:经纪账户
Table: BROKERAGE_ACCOUNT
表格:客户
Table: CUSTOMER
表格:投资
Table: INVESTMENT
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
// Omit constructors, etc.
public Customer getCustomer() {
String sqlQuery =
"SELECT * FROM CUSTOMER WHERE" +
"SS_NUMBER='"+customerSocialSecurityNumber+"'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Set getInvestments() {
String sqlQuery =
"SELECT * FROM INVESTMENT WHERE" +
"BROKERAGE_ACCOUNT='"+accountNumber+"'";
return QueryService.findInvestmentsFor(sqlQuery);
}
}
(注: QueryService 是一个用于从数据库中获取行并创建对象的实用程序,它虽然便于解释示例,但对于实际项目而言未必是一个好的设计。)
(Note: The QueryService, a utility for fetching rows from the database and creating objects, is simple for explaining examples, but it’s not necessarily a good design for a real project.)
让我们通过限定经纪账户和投资之间的关联性来改进模型,从而降低其多重性。这意味着每只股票只能对应一项投资。
Let’s refine the model by qualifying the association between Brokerage Account and Investment, reducing its multiplicity. This says there can be only one investment per stock.
图 5.4
Figure 5.4
这并非适用于所有业务场景(例如,如果需要跟踪批次),但无论具体规则如何,一旦发现关联约束,就应将其纳入模型和实现中。这样可以使模型更精确,实现更易于维护。
This wouldn’t be true of all business situations (for example, if the lots need to be tracked), but whatever the particular rules, as constraints on associations are discovered they should be included in the model and implementation. They make the model more precise and the implementation easier to maintain.
Java 实现可能如下所示:
The Java implementation could become:
public class BrokerageAccount {
String accountNumber;
Customer customer;
Map investments;
// Omitting constructors, etc.
public Customer getCustomer() {
return customer;
}
public Investment getInvestment(String stockSymbol) {
return (Investment)investments.get(stockSymbol);
}
}
基于 SQL 的实现方式如下:
And an SQL-based implementation would be:
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
//Omitting constructors, etc.
public Customer getCustomer() {
String sqlQuery = "SELECT * FROM CUSTOMER WHERE SS_NUMBER='"
+ customerSocialSecurityNumber + "'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Investment getInvestment(String stockSymbol) {
String sqlQuery = "SELECT * FROM INVESTMENT "
+ "WHERE BROKERAGE_ACCOUNT='" + accountNumber + "'"
+ "AND STOCK_SYMBOL='" + stockSymbol +"'";
return QueryService.findInvestmentFor(sqlQuery);
}
}
仔细提炼和约束模型的关联关系,将大大有助于你实现模型驱动设计。现在让我们来看看对象本身。某些区别能够阐明模型,并使实现更加实际……
Carefully distilling and constraining the model’s associations will take you a long way toward a MODEL-DRIVEN DESIGN. Now let’s turn to the objects themselves. Certain distinctions clarify the model while making for a more practical implementation. . . .
许多事物的本质并非由其属性决定,而是由某种连续性和同一性所决定。
Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity.
房东太太起诉我,声称我的房租严重损坏了她的房产。我收到的诉讼文件描述了一间公寓,墙壁上有洞,地毯上有污渍,水槽里有一种散发着腐蚀性气味的有害液体,导致厨房墙纸剥落。法院文件将我列为造成损失的租户,并指明了我的姓名和当时的住址。这让我很困惑,因为我从未去过那间破败不堪的房子。
A landlady sued me, claiming major damages to her property. The papers I was served described an apartment with holes in the walls, stains on the carpet, and a noxious liquid in the sink that gave off caustic fumes that had made the kitchen wallpaper peel. The court documents named me as the tenant responsible for the damages, identifying me by name and by my then-current address. This was confusing to me, because I had never even visited that ruined place.
过了一会儿,我意识到这一定是认错人了。我打电话给原告,告诉了她这件事,但她不相信。那个前房客已经躲了她好几个月了。我该怎么证明我不是那个让她损失那么多钱的人呢?电话簿上只有我一个叫埃里克·埃文斯的人。
After a moment, I realized that it must be a case of mistaken identity. I called the plaintiff and told her this, but she didn’t believe me. The former tenant had been eluding her for months. How could I prove that I was not the same person who had cost her so much money? I was the only Eric Evans in the phone book.
幸好电话簿救了我。因为我在同一个公寓住了两年,就问她是否还留着去年的电话簿。她找到电话簿后,确认我的信息和去年的一样(就在我同名的人旁边),这才意识到我不是她要起诉的人,于是向她道歉,并承诺撤销诉讼。
Well, the phone book turned out to be my salvation. Because I had been living in the same apartment for two years, I asked her if she still had the previous year’s book. After she found it and verified that my listing was the same (right next to my namesake’s listing), she realized that I was not the person she wanted to sue, apologized, and promised to drop the case.
计算机的适应能力并不强。软件系统中的身份识别错误会导致数据损坏和程序错误。
Computers are not that resourceful. A case of mistaken identity in a software system leads to data corruption and program errors.
这里存在一些特殊的技术挑战,我稍后会详细讨论,但首先让我们来看一个根本问题:许多事物是由其身份而非任何属性定义的。在我们通常的观念中,一个人(继续以非技术性的例子为例)的身份贯穿其从出生到死亡,甚至超越死亡。这个人的生理特征会发生变化,最终消失。名字可能会改变。财务关系会来来去去。一个人的任何属性都可能发生改变;然而,身份却始终存在。我还是五岁时的我吗?这种形而上学的问题在寻找有效的领域模型时至关重要。稍作改写:应用程序的用户会在意我是否还是五岁时的我吗?
There are special technical challenges here, which I’ll discuss in a bit, but first let’s look at the fundamental issue: Many things are defined by their identity, and not by any attribute. In our typical conception, a person (to continue with the nontechnical example) has an identity that stretches from birth to death and even beyond. That person’s physical attributes transform and ultimately disappear. The name may change. Financial relationships come and go. There is not a single attribute of a person that cannot change; yet the identity persists. Am I the same person I was at age five? This kind of metaphysical question is important in the search for effective domain models. Slightly rephrased: Does the user of the application care if I am the same person I was at age five?
在用于追踪应收账款的软件系统中,这个不起眼的“客户”对象可能有着更为丰富的一面。它会因及时付款而提升状态,或者因逾期付款而被移交给催收机构。当销售人员将客户数据提取到客户关系管理软件中时,它甚至可能在另一个系统中拥有双重身份。无论如何,它最终都会被毫不留情地压缩,存储在数据库表中。当新业务不再从该来源流入时,这个客户对象将被归档,成为昔日辉煌的影子。
In a software system for tracking accounts due, that modest “customer” object may have a more colorful side. It accumulates status by prompt payment or is turned over to a bill-collection agency for failure to pay. It may lead a double life in another system altogether when the sales force extracts customer data into its contact management software. In any case, it is unceremoniously squashed flat to be stored in a database table. When new business stops flowing from that source, the customer object will be retired to an archive, a shadow of its former self.
每种客户类型都是基于不同的编程语言和技术实现的。但是,当接到订单电话时,重要的是要知道:这是那个欠款的客户吗?这是杰克(某位销售代表)已经跟进数周的客户吗?还是一个全新的客户?
Each of these forms of the customer is a different implementation based on a different programming language and technology. But when a phone call comes in with an order, it is important to know: Is this the customer who has the delinquent account? Is this the customer that Jack (a particular sales representative) has been working with for weeks? Is this a completely new customer?
对象的多种实现方式、存储形式以及现实世界中的参与者(例如电话呼叫者)之间必须保持概念上的一致性。属性可能不匹配。例如,销售代表可能已在联系人软件中输入了地址更新,而该更新目前正被同步到待处理账户。两个客户联系人可能拥有相同的姓名。在分布式软件中,多个用户可能从不同的数据源输入数据,导致更新事务在系统中异步传播,并在不同的数据库中进行同步。
A conceptual identity has to be matched between multiple implementations of the objects, its stored forms, and real-world actors such as the phone caller. Attributes may not match. A sales representative may have entered an address update into the contact software, which is just being propagated to accounts due. Two customer contacts may have the same name. In distributed software, multiple users could be entering data from different sources, causing update transactions to propagate through the system to be reconciled in different databases asynchronously.
对象建模往往使我们关注对象的属性,但实体的基本概念是贯穿生命周期甚至经历多种形式的抽象连续性。
Object modeling tends to lead us to focus on the attributes of an object, but the fundamental concept of an ENTITY is an abstract continuity threading through a life cycle and even passing through multiple forms.
有些对象并非主要由其属性定义。它们代表着贯穿时间、且往往跨越不同表征的某种身份认同。有时,即使属性不同,这类对象也必须与其他对象进行匹配。即使属性相同,也必须将一个对象与其他对象区分开来。身份识别错误会导致数据损坏。
Some objects are not defined primarily by their attributes. They represent a thread of identity that runs through time and often across distinct representations. Sometimes such an object must be matched with another object even though attributes differ. An object must be distinguished from other objects even though they might have the same attributes. Mistaken identity can lead to data corruption.
主要由其身份定义的对象称为实体。实体在建模和设计方面有特殊的考量。它们的生命周期可能会彻底改变其形式和内容,但必须保持某种连续性。必须定义它们的身份,以便能够有效地跟踪它们。它们的类定义、职责、属性和关联应该围绕它们是谁展开,而不是它们所具有的特定属性。即使对于那些变化不大或生命周期不复杂的实体,将它们归入语义类别也能带来更清晰的模型和更健壮的实现。
An object defined primarily by its identity is called an ENTITY.1 ENTITIES have special modeling and design considerations. They have life cycles that can radically change their form and content, but a thread of continuity must be maintained. Their identities must be defined so that they can be effectively tracked. Their class definitions, responsibilities, attributes, and associations should revolve around who they are, rather than the particular attributes they carry. Even for ENTITIES that don’t transform so radically or have such complicated life cycles, placing them in the semantic category leads to more lucid models and more robust implementations.
当然,软件系统中的大多数“实体”并非通常意义上的人或实体。实体是指在生命周期中具有连续性,且其特征独立于对应用程序用户重要的属性的任何事物。它可以是人、城市、汽车、彩票或银行交易。
Of course, most “ENTITIES” in a software system are not people or entities in the usual sense of the word. An ENTITY is anything that has continuity through a life cycle and distinctions independent of attributes that are important to the application’s user. It could be a person, a city, a car, a lottery ticket, or a bank transaction.
另一方面,模型中的并非所有对象都是具有有意义标识的实体。这个问题之所以复杂,是因为面向对象语言在每个对象中都内置了“标识”操作(例如,==Java 中的 `&&` 运算符)。这些操作通过比较两个引用在内存中的位置或其他机制来确定它们是否指向同一个对象。从这个意义上讲,每个对象实例都具有标识。例如,在创建 Java 运行时环境或用于本地缓存远程对象的技术框架等领域,每个对象实例确实可能是一个实体。但这种标识并非实体。在其他应用领域,机制的意义微乎其微。身份是实体的一种微妙而重要的属性,它无法通过语言的自动功能来实现。
On the other hand, not all objects in the model are ENTITIES, with meaningful identities. This issue is confused by the fact that object-oriented languages build “identity” operations into every object (for example, the “==” operator in Java). These operations determine if two references point to the same object by comparing their location in memory or by some other mechanism. In this sense, every object instance has identity. In the domain of, say, creating a Java runtime environment or a technical framework for caching remote objects locally, every object instance may indeed be an ENTITY. But this identity mechanism means very little in other application domains. Identity is a subtle and meaningful attribute of ENTITIES, which can’t be turned over to the automatic features of the language.
考虑银行应用程序中的交易。同一天向同一账户存入两笔金额相同的款项,它们仍然是不同的交易,因此它们具有身份标识,是实体。另一方面,这两笔交易的金额属性很可能是某个货币对象的实例。这些值没有身份标识,因为区分它们没有意义。事实上,两个对象可以具有相同的身份标识,而无需具有相同的属性,甚至不一定属于同一类。当银行客户将银行对账单上的交易与支票登记簿上的交易进行核对时,具体来说,任务是匹配具有相同身份标识的交易,即使它们是由不同的人在不同的日期记录的(银行清算日期晚于支票上的日期)。支票号码的作用是作为唯一的标识符来实现这一目的,无论处理问题是由计算机程序还是人工完成。存款和取款没有识别号码,处理起来可能更复杂,但同样的原则也适用:每笔交易都是一个实体,它至少以两种形式出现。
Consider transactions in a banking application. Two deposits of the same amount to the same account on the same day are still distinct transactions, so they have identity and are ENTITIES. On the other hand, the amount attributes of those two transactions are probably instances of some money object. These values have no identity, since there is no usefulness in distinguishing them. In fact, two objects can have the same identity without having the same attributes or even, necessarily, being of the same class. When the bank customer is reconciling the transactions of the bank statement with the transactions of the check registry, the task is, specifically, to match transactions that have the same identity, even though they were recorded by different people on different dates (the bank clearing date being later than the date on the check). The purpose of the check number is to serve as a unique identifier for this purpose, whether the problem is being handled by a computer program or by hand. Deposits and cash withdrawals, which don’t have an identifying number, can be trickier, but the same principle applies: each transaction is an ENTITY, which appears in at least two forms.
身份信息在特定软件系统之外也具有重要意义,例如银行交易和公寓租户。但有时身份信息仅在系统上下文中才重要,例如计算机进程的身份信息。
It is common for identity to be significant outside a particular software system, as is the case with the banking transactions and the apartment tenants. But sometimes the identity is important only in the context of the system, such as the identity of a computer process.
所以:
Therefore:
当对象通过其身份而非属性来区分时,应将此作为模型中定义对象的首要考虑因素。保持类定义简洁,并专注于生命周期连续性和身份标识。定义一种区分每个对象的方法,无论其形式或历史如何。注意那些要求按属性匹配对象的需求。定义一个操作,保证为每个对象产生唯一结果,例如通过附加一个保证唯一的符号。这种标识方法可以来自外部,也可以是系统创建并用于系统的任意标识符,但它必须与模型中的身份区分相对应。模型必须定义“ 同一事物”的含义。
When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model. Keep the class definition simple and focused on life cycle continuity and identity. Define a means of distinguishing each object regardless of its form or history. Be alert to requirements that call for matching objects by attributes. Define an operation that is guaranteed to produce a unique result for each object, possibly by attaching a symbol that is guaranteed unique. This means of identification may come from the outside, or it may be an arbitrary identifier created by and for the system, but it must correspond to the identity distinctions in the model. The model must define what it means to be the same thing.
身份并非事物固有的属性;它是一种因实用性而赋予的意义。事实上,同一个现实世界的事物在领域模型中可能被表示为实体,也可能不被表示为实体。
Identity is not intrinsic to a thing in the world; it is a meaning superimposed because it is useful. In fact, the same real-world thing might or might not be represented as an ENTITY in a domain model.
用于预订体育场座位的应用程序可能会将座位和观众视为实体。在指定座位的情况下,每张票上都有一个座位号,座位本身就是一个实体。座位号是其唯一标识符,在整个体育场内都是唯一的。座位可能还有许多其他属性,例如位置、视野是否受阻以及价格,但只有座位号,或者唯一的排号和位置,才用于识别和区分座位。
An application for booking seats in a stadium might treat seats and attendees as ENTITIES. In the case of assigned seating, in which each ticket has a seat number on it, the seat is an ENTITY. Its identifier is the seat number, which is unique within the stadium. The seat may have many other attributes, such as its location, whether the view is obstructed, and the price, but only the seat number, or a unique row and position, is used to identify and distinguish seats.
另一方面,如果活动是“自由入场”,即持票人可以随意选择空位就座,则无需区分具体座位,只需统计座位总数即可。虽然座位号仍然刻在座椅上,但软件无需追踪这些座位号。事实上,模型将特定座位号与门票关联起来是错误的,因为自由入场活动没有这样的限制。在这种情况下,座位并非实体 ,因此无需任何标识符。
On the other hand, if the event is “general admission,” meaning that ticket holders sit wherever they find an empty seat, there is no need to distinguish individual seats. Only the total number of seats is important. Although the seat numbers are still engraved on the physical seats, there is no need for the software to track them. In fact, it would be erroneous for the model to associate specific seat numbers with tickets, because there is no such constraint at a general admission event. In such a case, seats are not ENTITIES, and no identifier is needed.
在对对象进行建模时,考虑其属性是自然而然的,而考虑其行为也至关重要。但实体最基本的职责是建立连续性,从而使行为清晰可预测。保持简洁是实现这一目标的最佳方式。与其关注属性甚至行为,不如将实体对象的定义精简到最内在的特征,特别是那些能够识别它或通常用于查找或匹配它的特征。仅添加对概念至关重要的行为以及该行为所需的属性。除此之外,应考虑将行为和属性转移到与核心实体关联的其他对象中。其中一些对象本身也是实体,而另一些则是值对象,这是本章的下一个主题。除了身份问题之外,实体通常通过协调其所拥有对象的操作来履行其职责。
It is natural to think about the attributes when modeling an object, and it is quite important to think about its behavior. But the most basic responsibility of ENTITIES is to establish continuity so that behavior can be clear and predictable. They do this best if they are kept spare. Rather than focusing on the attributes or even the behavior, strip the ENTITY object’s definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it. Add only behavior that is essential to the concept and attributes that are required by that behavior. Beyond that, look to remove behavior and attributes into other objects associated with the core ENTITY. Some of these will be other ENTITIES. Some will be VALUE OBJECTS, which is the next pattern in this chapter. Beyond identity issues, ENTITIES tend to fulfill their responsibilities by coordinating the operations of objects they own.
在图 5.5中,customerID 是Customer 实体的唯一标识符,但电话号码和地址通常也用于查找或匹配客户。姓名本身并不定义一个人的身份,但通常用作确定身份的手段之一。在本例中,电话号码和地址属性被移到了Customer 实体中,但在实际项目中,这种选择取决于该领域通常如何匹配或区分客户。例如,如果一个客户出于不同目的拥有多个联系电话号码,那么电话号码与身份无关,应该保留在Sales Contact实体中。
The customerID is the one and only identifier of the Customer ENTITY in Figure 5.5, but the phone number and address would often be used to find or match a Customer. The name does not define a person’s identity, but it is often used as part of the means of determining it. In this example, the phone and address attributes moved into Customer, but on a real project, that choice would depend on how the domain’s customers are typically matched or distinguished. For example, if a Customer has many contact phone numbers for different purposes, then the phone number is not associated with identity and should stay with the Sales Contact.
图 5.5. 与身份相关的属性保留在实体中。
Figure 5.5. Attributes associated with identity stay with the ENTITY.
每个实体都必须有一种可操作的方法来确立其与其他对象之间的身份——即使与其他具有相同描述属性的对象相比,这种身份也必须能够被区分。无论系统如何定义,都必须保证标识属性在系统中是唯一的——即使是分布式系统,即使对象已被归档。
Each ENTITY must have an operational way of establishing its identity with another object—distinguishable even from another object with the same descriptive attributes. An identifying attribute must be guaranteed to be unique within the system however that system is defined—even if distributed, even when objects are archived.
如前所述,面向对象语言具有“标识”操作,它通过比较对象在内存中的位置来确定两个引用是否指向同一个对象。这种标识跟踪方式对于我们的需求来说过于脆弱。在大多数对象持久存储技术中,每次从数据库检索对象时,都会创建一个新的实例,从而丢失初始标识。每次通过网络传输对象时,都会在目标端创建一个新的实例,标识再次丢失。当系统中存在同一对象的多个版本时,问题可能会更加严重,例如当更新通过分布式数据库传播时。
As mentioned earlier, object-oriented languages have “identity” operations that determine if two references point to the same object by comparing the objects’ locations in memory. This kind of identity tracking is too fragile for our purposes. In most technologies for persistent storage of objects, every time an object is retrieved from a database, a new instance is created, and so the initial identity is lost. Every time an object is transmitted across a network, a new instance is created on the destination, and once again the identity is lost. The problem can be even worse when multiple versions of the same object exist in the system, such as when updates propagate through a distributed database.
即使有了能够简化这些技术问题的框架,根本问题依然存在:如何确定两个对象代表的是同一个概念实体?身份的定义源于模型。而定义身份则需要对领域有深刻的理解。
Even with frameworks that simplify these technical problems, the fundamental issue exists: How do you know that two objects represent the same conceptual ENTITY? The definition of identity emerges from the model. Defining identity demands understanding of the domain.
有时,某些数据属性或属性组合可以保证或简单地约束为在系统中是唯一的。这种方法为实体提供了一个唯一的键。例如,日报可以通过报纸名称、城市和出版日期来标识。(但要注意增刊和名称变更!)
Sometimes certain data attributes, or combinations of attributes, can be guaranteed or simply constrained to be unique within the system. This approach provides a unique key for the ENTITY. Daily newspapers, for example, might be identified by the name of the newspaper, the city, and the date of publication. (But watch out for extra editions and name changes!)
当对象属性无法构成真正的唯一键时,另一种常见的解决方案是为每个实例附加一个在类中唯一的符号(例如数字或字符串)。一旦创建此 ID 符号并将其存储为实体的属性,它就被指定为不可变的。即使开发系统无法直接强制执行此规则,它也绝不能更改。例如,当对象被扁平化到数据库并重新构建时,ID 属性会被保留。有时技术框架可以帮助完成此过程,但通常情况下,这需要严谨的工程规范。
When there is no true unique key made up of the attributes of an object, another common solution is to attach to each instance a symbol (such as a number or a string) that is unique within the class. Once this ID symbol is created and stored as an attribute of the ENTITY, it is designated immutable. It must never change, even if the development system is unable to directly enforce this rule. For example, the ID attribute is preserved as the object gets flattened into a database and reconstructed. Sometimes a technical framework helps with this process, but otherwise it just takes engineering discipline.
通常情况下,ID 由系统自动生成。生成算法必须保证系统内的唯一性,这在并发处理和分布式系统中可能是一个挑战。生成此类 ID 可能需要超出本书范围的技术。本文旨在指出何时需要考虑这些问题,以便开发人员意识到他们需要解决的问题,并知道如何将关注点缩小到关键领域。关键在于认识到身份问题取决于模型的特定方面。通常,身份识别方法也需要对领域进行仔细研究。
Often the ID is generated automatically by the system. The generation algorithm must guarantee uniqueness within the system, which can be a challenge with concurrent processing and in distributed systems. Generating such an ID may require techniques that are beyond the scope of this book. The goal here is to point out when the considerations arise, so that developers are aware they have a problem to solve and know how to narrow down their concerns to the critical areas. The key is to recognize that identity concerns hinge on specific aspects of the model. Often, the means of identification demand a careful study of the domain, as well.
当 ID 是自动生成的,用户可能永远不需要看到它。ID 可能仅供内部使用,例如在联系人管理应用程序中,用户可以按姓名查找记录。程序需要能够以简单、明确的方式区分两个姓名完全相同的联系人。唯一的内部 ID 正好可以让系统做到这一点。检索到如果是两个不同的项目,系统会向用户显示两个不同的联系人,但可能不会显示 ID。用户可以根据公司、位置等信息来区分它们。
When the ID is automatically generated, the user may never need to see it. The ID may be needed only internally, such as in a contact management application that lets the user find records by a person’s name. The program needs to be able to distinguish two contacts with exactly the same name in a simple, unambiguous way. The unique, internal IDs let the system do just that. After retrieving the two distinct items, the system will show two separate contacts to the user, but the IDs may not be shown. The user will distinguish them on the basis of their company, their location, and so on.
最后,有些情况下,生成的ID对用户来说也很有用。例如,当我通过包裹递送服务寄送包裹时,我会收到一个由递送公司软件生成的追踪号码,我可以用这个号码来识别和追踪我的包裹。当我预订机票或酒店时,我会收到确认号码,这些号码是该笔交易的唯一标识符。
Finally, there are cases in which a generated ID is of interest to the user. When I ship a package through a parcel delivery service, I’m given a tracking number, generated by the shipping company’s software, which I can use to identify and follow up on my package. When I book airline tickets or reserve a hotel, I’m given confirmation numbers that are unique identifiers for the transaction.
在某些情况下,ID的唯一性必须超越计算机系统的边界。例如,如果两家医院使用独立的计算机系统交换医疗记录,理想情况下,每个系统都应使用相同的患者ID,但如果它们各自生成自己的符号,则难以实现。此类系统通常使用其他机构(通常是政府机构)颁发的标识符。在美国,医院经常使用社会安全号码作为个人标识符。但这种方法并非万无一失。并非每个人都有社会安全号码(尤其是儿童和非美国居民),而且许多人出于隐私原因反对使用社会安全号码。
In some cases, the uniqueness of the ID must apply beyond the computer system’s boundaries. For example, if medical records are being exchanged between two hospitals that have separate computer systems, ideally each system will use the same patient ID, but this is difficult if they generate their own symbol. Such systems often use an identifier issued by some other institution, typically a government agency. In the United States, the Social Security number is often used by hospitals as an identifier for a person. Such methods are not foolproof. Not everyone has a Social Security number (children and nonresidents of the United States, especially), and many people object to its use, for privacy reasons.
在不太正式的场合(例如录像带租赁),电话号码用作身份标识。但电话可以共用,号码可能会变更,甚至旧号码可以重新分配给其他人。
In less formal situations (say, video rental), telephone numbers are used as identifiers. But a telephone can be shared. The number can change. An old number can even be reassigned to a different person.
因此,通常会使用专门分配的标识符(例如常旅客号码),并使用其他属性(例如电话号码和社会安全号码)进行匹配和验证。无论如何,当应用程序需要外部 ID 时,系统用户有责任提供唯一的 ID,并且系统必须为他们提供足够的工具来处理出现的异常情况。
For these reasons, specially assigned identifiers are often used (such as frequent flier numbers), and other attributes, such as phone numbers and Social Security numbers, are used to match and verify. In any case, when the application requires an external ID, the users of the system become responsible for supplying IDs that are unique, and the system must give them adequate tools to handle exceptions that arise.
鉴于所有这些技术难题,人们很容易忽略其根本的概念问题:两个对象如何才能算是同一事物?给每个对象贴上一个ID标签,或者编写一个比较两个实例的操作固然容易,但如果这些ID或操作在领域内没有对应任何有意义的区分,只会让事情更加混乱。这就是为什么身份分配操作通常需要人工干预的原因。例如,支票簿核对软件可能会提供可能的匹配结果,但最终的判断仍需由用户做出。
Given all these technical problems, it is easy to lose sight of the underlying conceptual problem: What does it mean for two objects to be the same thing? It is easy enough to stamp each object with an ID, or to write an operation that compares two instances, but if these IDs or operations don’t correspond to some meaningful distinction in the domain, they just confuse matters more. This is why identity-assigning operations often involve human input. Checkbook reconciliation software, for instance, may offer likely matches, but the user is expected to make the final determination.
许多物体没有概念上的同一性。这些物体描述的是事物的某些特征。
Many objects have no conceptual identity. These objects describe some characteristic of a thing.
孩子画画时,会很在意自己选择的马克笔颜色,也可能在意笔尖是否锋利。但如果两支马克笔颜色和形状都一样,他可能就不会在意用哪一支了。如果一支马克笔丢了,换上新包装里同色的一支,他也能继续画画,而不会在意笔的更换。
When a child is drawing, he cares about the color of the marker he chooses, and he may care about the sharpness of the tip. But if there are two markers of the same color and shape, he probably won’t care which one he uses. If a marker is lost and replaced by another of the same color from a new pack, he can resume his work unconcerned about the switch.
问问孩子冰箱上不同的画作,他很快就能分辨出哪些是他画的,哪些是他妹妹画的。他和妹妹的身份信息很有用,他们完成的画作也是如此。但想象一下,如果他必须追踪画中每条线分别由谁画的,那该有多复杂。画画就不再是孩子的游戏了。
Ask the child about the various drawings on the refrigerator, and he will quickly distinguish those he made from those his sister made. He and his sister have useful identities, as do their completed drawings. But imagine how complicated it would be if he had to track which lines in a drawing were made by each marker. Drawing would no longer be child’s play.
由于模型中最显眼的对象通常是实体,而且追踪每个实体的身份至关重要,因此很自然地会考虑为所有领域对象分配一个身份。实际上,一些框架会为每个对象分配一个唯一的 ID。
Because the most conspicuous objects in a model are usually ENTITIES, and because it is so important to track each ENTITY’s identity, it is natural to consider assigning an identity to all domain objects. Indeed, some frameworks assign a unique ID to every object.
该系统必须应对所有这些追踪工作,许多可能的性能优化方案都被排除在外。需要投入分析精力来定义有意义的身份,并制定万无一失的追踪方法。对象可以跨分布式系统或存储在数据库中找到。同样重要的是,赋予对象人为的身份会产生误导。这会混淆模型,强迫所有对象都采用相同的模式。
The system has to cope with all that tracking, and many possible performance optimizations are ruled out. Analytical effort is required to define meaningful identities and work out foolproof ways to track objects across distributed systems or in database storage. Equally important, taking on artificial identities is misleading. It muddles the model, forcing all objects into the same mold.
追踪实体的身份至关重要,但将身份附加到其他对象可能会损害系统性能,增加分析工作量,并使所有对象看起来都一样,从而混淆模型。
Tracking the identity of ENTITIES is essential, but attaching identity to other objects can hurt system performance, add analytical work, and muddle the model by making all objects look the same.
软件设计是一场与复杂性的持续斗争。我们必须加以区分,以便仅在必要时才进行特殊处理。
Software design is a constant battle with complexity. We must make distinctions so that special handling is applied only where necessary.
然而,如果我们仅仅将这类对象视为身份的缺失,那么我们的工具箱或词汇量并没有增加多少。事实上,这些对象本身具有特征,并且对模型具有特殊的意义。 它们才是描述事物的对象。
However, if we think of this category of object as just the absence of identity, we haven’t added much to our toolbox or vocabulary. In fact, these objects have characteristics of their own and their own significance to the model. These are the objects that describe things.
表示领域描述性方面但没有概念标识的对象称为值对象。值对象被实例化以表示设计中的元素,我们只关心它们是什么,而不关心它们是谁或属于哪个类别。
An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT. VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.
颜色是许多现代开发系统基础库中提供的“值对象”的一个例子;字符串和数字也是如此。(你不需要关心你拥有的是哪个“4”或哪个“Q”。)这些基本示例很简单,但“值对象”本身并不一定简单。例如,一个颜色混合程序可能包含一个复杂的模型,其中可以将增强的颜色对象组合起来生成其他颜色。这些颜色可能需要复杂的算法来协同工作,从而得出新的结果“值对象”。
Colors are an example of VALUE OBJECTS that are provided in the base libraries of many modern development systems; so are strings and numbers. (You don’t care which “4” you have or which “Q”.) These basic examples are simple, but VALUE OBJECTS are not necessarily simple. For example, a color-mixing program might have a rich model in which enhanced color objects could be combined to produce other colors. These colors could have complex algorithms for collaborating to derive the new resulting VALUE OBJECT.
值对象可以由其他对象组成。在房屋平面图设计软件中,可以为每种窗户样式创建一个对象。这种“窗户样式”可以与高度、宽度以及控制这些属性如何更改和组合的规则一起,整合到一个“窗户”对象中。这些窗户是复杂的值对象,由其他值对象构成。它们又可以整合到平面图中更大的元素中,例如“墙”对象。
A VALUE OBJECT can be an assemblage of other objects. In software for designing house plans, an object could be created for each window style. This “window style” could be incorporated into a “window” object, along with height and width, as well as rules governing how these attributes can be changed and combined. These windows are intricate VALUE OBJECTS made up of other VALUE OBJECTS. They in turn would be incorporated into larger elements of a plan, such as “wall” objects.
值对象甚至可以引用实体。例如,如果我向在线地图服务查询从旧金山到洛杉矶的风景驾车路线,它可能会生成一个路线对象,该路线对象通过太平洋海岸公路连接洛杉矶和旧金山。该路线对象就是一个值。即使它引用的三个对象(两个城市和一条高速公路)都是实体。
VALUE OBJECTS can even reference ENTITIES. For example, if I ask an online map service for a scenic driving route from San Francisco to Los Angeles, it might derive a Route object linking L.A. and San Francisco via the Pacific Coast Highway. That Route object would be a VALUE, even though the three objects it references (two cities and a highway) are all ENTITIES.
值对象通常作为参数在对象间的消息传递中传递。它们通常是瞬态的,为某个操作创建后即被丢弃。值对象用作实体(以及其他值)的属性。例如,一个人可以被建模为具有身份的实体,但这个人的姓名却是一个值。
VALUE OBJECTS are often passed as parameters in messages between objects. They are frequently transient, created for an operation and then discarded. VALUE OBJECTS are used as attributes of ENTITIES (and other VALUES). A person may be modeled as an ENTITY with an identity, but that person’s name is a VALUE.
当您只关心模型中某个元素的属性时,请将其归类为值对象 (VALUE OBJECT)。使其表达其属性的含义,并赋予其相关的功能。将值对象视为不可变对象。不要为其赋予任何标识,从而避免维护实体 (ENTITIES)所需的复杂设计。
When you care only about the attributes of an element of the model, classify it as a VALUE OBJECT. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the VALUE OBJECT as immutable. Don’t give it any identity and avoid the design complexities necessary to maintain ENTITIES.
构成值对象的属性应该形成一个概念整体。例如,街道、城市和邮政编码不应该是 Person 对象的独立属性。它们是单个完整地址的一部分,这使得 Person 对象更简洁,值对象也更连贯。
The attributes that make up a VALUE OBJECT should form a conceptual whole.2 For example, street, city, and postal code shouldn’t be separate attributes of a Person object. They are part of a single, whole address, which makes a simpler Person, and a more coherent VALUE OBJECT.
图 5.6.值对象可以提供关于实体的信息。它在概念上应该是完整的。
Figure 5.6. A VALUE OBJECT can give information about an ENTITY. It should be conceptually whole.
我们并不关心值对象的具体实例是什么。这种不受约束的特性赋予了我们设计上的自由,我们可以利用这种自由来简化设计或优化性能。这涉及到对复制、共享和不可变性的选择。
We don’t care which instance we have of a VALUE OBJECT. This lack of constraints gives us design freedom we can use to simplify the design or optimize performance. This involves making choices about copying, sharing, and immutability.
如果两个人同名,这并不意味着他们是同一个人,也不能互换。但是代表名字的对象是可以互换的,因为只有名字的拼写才重要。可以将第一个 Person 对象中的 Name 对象复制到第二个 Person 对象中。
If two people have the same name, that does not make them the same person, or make them interchangeable. But the object representing the name is interchangeable, because only the spelling of the name matters. A Name object can be copied from the first Person object to the second.
事实上,这两个 Person 对象可能并不需要各自独立的 name 实例。同一个 Name 对象可以在两个 Person 对象之间共享(每个对象都指向同一个 name 实例),而不会改变它们的行为或身份。也就是说,在其中一个人的姓名发生更改之前,它们的行为都是正确的。一旦更改,另一个人的姓名也会随之改变!为了防止这种情况发生,为了确保对象能够安全地共享,它必须是不可变的:除了完全替换之外,不能对其进行任何更改。
In fact, the two Person objects might not need their own name instances. The same Name object could be shared between the two Person objects (each with a pointer to the same name instance) with no change in their behavior or identity. That is, their behavior will be correct until some change is made to the name of one person. Then the other person’s name would change also! To protect against this, in order for an object to be shared safely, it must be immutable: it cannot be changed except by full replacement.
当一个对象将其属性作为参数或返回值传递给另一个对象时,也会出现同样的问题。在脱离其所有者控制期间,该对象可能会发生任何事情。其值可能会被修改,从而破坏所有者的属性,违反所有者的不变式。避免此问题的方法是使传递的对象不可变,或者传递一个副本。
The same issues arise when an object passes one of its attributes to another object as an argument or return value. Anything could happen to the wandering object while it is out of control of its owner. The VALUE could be changed in a way that corrupts the owner, by violating the owner’s invariants. This problem is avoided either by making the passed object immutable, or by passing a copy.
创建额外的性能调优选项至关重要,因为值对象往往数量众多。房屋设计软件的例子就说明了这一点。如果每个电源插座都是一个独立的值对象,那么在同一个房屋平面图的单个版本中,可能就存在上百个插座。但如果所有插座都被视为可互换的,我们就可以共享一个插座实例,并多次指向它(这是FLYWEIGHT的一个例子[ Gamma等人,1995 ])。在大型系统中,这种效果可以放大数千倍,而这种优化可以决定一个系统是可用还是因为数百万个冗余对象而运行缓慢。这只是实体无法使用的优化技巧的一个例子。
Creating extra options for performance tuning can be important because VALUE OBJECTS tend to be numerous. The example of the house design software hints at this. If each electrical outlet is a separate VALUE OBJECT, there might be a hundred of them in a single version of a single house plan. But if all outlets are considered interchangeable, we could share just one instance of an outlet and point to it a hundred times (an example of FLYWEIGHT [Gamma et al. 1995]). In large systems, this kind of effect can be multiplied by thousands, and such an optimization can make the difference between a usable system and one that slows to a crawl, choked on millions of redundant objects. This is just one example of an optimization trick that is not available for ENTITIES.
复制与共享的经济性取决于具体的实现环境。虽然复制操作可能会导致系统被大量对象阻塞,但共享操作可能会降低分布式系统的速度。当在两台机器之间传递副本时,只需发送一条消息,副本即可独立存在于接收机器上。但如果共享的是单个实例,则只会传递一个引用,每次交互都需要向该对象发送一条消息。
The economy of copying versus sharing depends on the implementation environment. Although copies may clog the system with huge numbers of objects, sharing can slow down a distributed system. When a copy is passed between two machines, a single message is sent and the copy lives independently on the receiving machine. But if a single instance is being shared, only a reference is passed, requiring a message back to the object for each interaction.
Sharing is best restricted to those cases in which it is most valuable and least troublesome:
• 当节省数据库空间或对象数量至关重要时
• When saving space or object count in the database is critical
• 当通信开销较低时(例如在集中式服务器中)
• When communication overhead is low (such as in a centralized server)
• 当共享对象严格不可变时
• When the shared object is strictly immutable
在某些语言和环境中,属性或对象的不可变性可以声明,但在其他语言和环境中则不行。这类特性有助于传达设计决策,但并非必不可少。在大多数现有工具和编程语言中,我们在模型中做出的许多区分都无法在实现中显式声明。例如,您无法声明实体(ENTITIES),然后自动强制执行标识操作。但是,缺乏对概念区分的直接语言支持并不意味着这种区分没有用处。这只是意味着我们需要更多规范来维护那些在实现中仅隐式存在的规则。命名约定、选择性文档和大量讨论可以强化这一点。
Immutability of an attribute or an object can be declared in some languages and environments but not in others. Such features help communicate the design decision, but they are not essential. Many of the distinctions we are making in the model cannot be explicitly declared in the implementation with most current tools and programming languages. You can’t declare ENTITIES, for example, and then have an identity operation automatically enforced. But the lack of direct language support for a conceptual distinction does not mean that the distinction is not useful. It just means that more discipline is needed to maintain the rules that will be only implicit in the implementation. This can be reinforced with naming conventions, selective documentation, and lots of discussion.
只要值对象是不可变的,变更管理就很简单——除了完全替换之外,不会发生任何变更。不可变对象可以自由共享,就像电源插座的例子一样。如果垃圾回收机制可靠,删除操作只需释放所有对该对象的引用即可。当值对象在设计中被指定为不可变时,开发人员可以完全从技术层面来决定诸如复制和共享之类的问题,因为他们知道应用程序并不依赖于对象的特定实例。
As long as a VALUE OBJECT is immutable, change management is simple—there isn’t any change except full replacement. Immutable objects can be freely shared, as in the electrical outlet example. If garbage collection is reliable, deletion is just a matter of dropping all references to the object. When a VALUE OBJECT is designated immutable in the design, developers are free to make decisions about issues such as copying and sharing on a purely technical basis, secure in the knowledge that the application does not rely on particular instances of the objects.
定义值对象并将其指定为不可变对象,遵循的是一条通用原则:避免模型中不必要的约束,可以让开发人员专注于纯粹的技术性能调优。明确定义必要的约束,则允许开发人员在不改变重要行为的前提下调整设计。此类设计调整通常与特定项目所使用的技术密切相关。
Defining VALUE OBJECTS and designating them as immutable is a case of following a general rule: Avoiding unnecessary constraints in a model leaves developers free to do purely technical performance tuning. Explicitly defining the essential constraints lets developers tweak the design while keeping safe from changing meaningful behavior. Such design tweaks are often very specific to the technology in use on a particular project.
从最底层来看,数据库必须将数据存储在磁盘上的某个物理位置,而物理部件移动和读取数据需要时间。更复杂的数据库会尝试将这些物理地址进行聚类,以便通过一次物理操作从磁盘中提取相关数据。
Databases, at the lowest level, have to place data in a physical location on a disk, and it takes time for physical parts to move around and read that data. Sophisticated databases attempt to cluster these physical addresses so that related data can be fetched from the disk in a single physical operation.
如果一个对象被许多其他对象引用,那么其中一些对象可能并不位于同一页面,需要额外的物理操作才能获取数据。通过创建副本,而不是共享对同一实例的引用,可以将作为多个实体属性的值对象存储在与每个使用它的实体相同的页面上。这种存储同一数据多个副本的技术称为反规范化,通常用于访问时间比存储空间或维护便捷性更重要的场景。
If an object is referenced by many other objects, some of those objects will not be located nearby (on the same page), requiring an additional physical operation to get the data. By making a copy, rather than sharing a reference to the same instance, a VALUE OBJECT that is acting as an attribute of many ENTITIES can be stored on the same page as each ENTITY that uses it. This technique of storing multiple copies of the same data is called denormalization and is often used when access time is more critical than storage space or simplicity of maintenance.
在关系数据库中,您可能希望将某个特定的值(VALUE)放入拥有它的实体(ENTITY)的表中,而不是将其关联到单独的表。在分布式系统中,将值对象(VALUE OBJECT)的引用保存在另一个服务器上可能会导致消息响应速度变慢;因此,应该将整个对象的副本传递给另一个服务器。由于我们处理的是值对象(VALUE OBJECT) ,因此可以自由地创建这些副本。
In a relational database, you might want to put a particular VALUE in the table of the ENTITY that owns it, rather than creating an association to a separate table. In a distributed system, holding a reference to a VALUE OBJECT on another server will probably make for slow responses to messages; instead, a copy of the whole object should be passed to the other server. We can freely make these copies because we are dealing with VALUE OBJECTS.
前面关于关联的大部分讨论同样适用于实体和值对象。模型中的关联越少越简单越好。
Most of the earlier discussion of associations applies to ENTITIES and VALUE OBJECTS alike. The fewer and simpler the associations in the model, the better.
但是,尽管实体之间的双向关联可能难以维护,两个值对象之间的双向关联则毫无意义。如果没有标识,说一个对象指向指向它的同一个值对象是没有意义的。你最多只能说它指向一个与指向它的对象相等的对象,但你必须在某个地方强制执行这个不变性。虽然你可以这样做,并设置双向指针,但很难想到这种安排的实际应用场景。这样做会很有帮助。尽量完全消除值对象之间的双向关联。如果最终你的模型中确实需要这种关联,那么请重新考虑当初将该对象声明为值对象的决定。也许它具有尚未被明确识别的身份。
But, while bidirectional associations between ENTITIES may be hard to maintain, bidirectional associations between two VALUE OBJECTS just make no sense. Without identity, it is meaningless to say that an object points back to the same VALUE OBJECT that points to it. The most you could say is that it points to an object that is equal to the one pointing to it, but you would have to enforce that invariant somewhere. And although you could do so, and set up pointers going both ways, it is hard to think of examples where such an arrangement would be useful. Try to completely eliminate bidirectional associations between VALUE OBJECTS. If in the end such associations seem necessary in your model, rethink the decision to declare the object a VALUE OBJECT in the first place. Maybe it has an identity that hasn’t been explicitly recognized yet.
实体和值对象是传统对象模型的主要元素,但务实的设计师已经开始使用另一个元素,即服务……
ENTITIES and VALUE OBJECTS are the main elements of conventional object models, but pragmatic designers have come to use one other element, SERVICES. . . .
Sometimes, it just isn’t a thing.
在某些情况下,最清晰、最务实的设计方案会包含一些在概念上不属于任何对象的操作。与其强行定义,不如顺应问题空间的自然轮廓,将“服务”明确地纳入模型中。
In some cases, the clearest and most pragmatic design includes operations that do not conceptually belong to any object. Rather than force the issue, we can follow the natural contours of the problem space and include SERVICES explicitly in the model.
有些重要的领域操作无法自然地归入实体或值对象中。其中一些本质上是活动或动作,而不是事物,但由于我们的建模范式是对象,我们还是会尝试将它们放入对象中。
There are important domain operations that can’t find a natural home in an ENTITY or VALUE OBJECT. Some of these are intrinsically activities or actions, not things, but since our modeling paradigm is objects, we try to fit them into objects anyway.
现在,更常见的错误是轻易放弃将行为适配到合适的对象中,逐渐滑向过程式编程。但是,当我们强行将一个操作塞进一个不符合对象定义的对象时,该对象就会失去概念上的清晰度,变得难以理解或重构。复杂的操作很容易淹没一个简单的对象,使其功能变得模糊不清。而且,由于这些操作通常会将许多领域对象组合在一起,协调它们并付诸行动,因此增加的职责会造成对所有这些对象的依赖,使原本可以独立理解的概念变得混乱不堪。
Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming. But when we force an operation into an object that doesn’t fit the object’s definition, the object loses its conceptual clarity and becomes hard to understand or refactor. Complex operations can easily swamp a simple object, obscuring its role. And because these operations often draw together many domain objects, coordinating them and putting them into action, the added responsibility will create dependencies on all those objects, tangling concepts that could be understood independently.
有时,服务会伪装成模型对象,看起来像是除了执行某些操作之外没有任何其他意义的对象。这些“执行者”最终会被赋予以“管理器”等结尾的名称。它们本身没有状态,除了它们所承载的操作之外,在领域内也没有任何其他意义。不过,至少这种解决方案为这些不同的行为提供了一个归宿,而不会破坏实际的模型对象。
Sometimes services masquerade as model objects, appearing as objects with no meaning beyond doing some operation. These “doers” end up with names ending in “Manager” and the like. They have no state of their own nor any meaning in the domain beyond the operation they host. Still, at least this solution gives these distinct behaviors a home without messing up a real model object.
领域中的某些概念并不适合建模为对象。强行将所需的领域功能交给实体或值来承担,要么会扭曲基于模型的对象的定义,要么会引入毫无意义的人为对象。
Some concepts from the domain aren’t natural to model as objects. Forcing the required domain functionality to be the responsibility of an ENTITY or VALUE either distorts the definition of a model-based object or adds meaningless artificial objects.
服务(SERVICE)是一种作为接口提供的操作,它在模型中独立存在,不像实体(ENTITIES)和值对象(VALUE OBJECTS)那样封装状态。服务是技术框架中常见的模式,但它们也可以应用于领域层。
A SERVICE is an operation offered as an interface that stands alone in the model, without encapsulating state, as ENTITIES and VALUE OBJECTS do. SERVICES are a common pattern in technical frameworks, but they can also apply in the domain layer.
服务名称强调与其他对象的关系。与实体和值对象不同,服务名称完全基于其能为客户端做什么来定义。服务名称通常指代某种活动,而非实体——是动词而非名词。服务仍然可以拥有抽象的、意图明确的定义;只是其表达方式与对象定义有所不同。服务仍然应该具有明确的职责,并且该职责及其实现接口应作为领域模型的一部分进行定义。操作名称应来自通用语言(UBIQUITOUS LANGUAGE)或被引入其中。参数和结果应为领域对象。
The name service emphasizes the relationship with other objects. Unlike ENTITIES and VALUE OBJECTS, it is defined purely in terms of what it can do for a client. A SERVICE tends to be named for an activity, rather than an entity—a verb rather than a noun. A SERVICE can still have an abstract, intentional definition; it just has a different flavor than the definition of an object. A SERVICE should still have a defined responsibility, and that responsibility and the interface fulfilling it should be defined as part of the domain model. Operation names should come from the UBIQUITOUS LANGUAGE or be introduced into it. Parameters and results should be domain objects.
服务(Service)应谨慎使用,不应剥夺实体(Entity)和值对象(Value Object)的所有行为。但当某个操作本身就是一个重要的领域概念时,服务就自然而然地成为模型驱动设计(Model-Driven Design)的一部分。在模型中将其声明为服务,而不是声明为实际上不代表任何内容的虚假对象,这样独立的操作就不会误导任何人。
SERVICES should be used judiciously and not allowed to strip the ENTITIES and VALUE OBJECTS of all their behavior. But when an operation is actually an important domain concept, a SERVICE forms a natural part of a MODEL-DRIVEN DESIGN. Declared in the model as a SERVICE, rather than as a phony object that doesn’t actually represent anything, the standalone operation will not mislead anyone.
好的服务具有三个特点。
A good SERVICE has three characteristics.
1.该操作与领域概念相关,而该领域概念不是实体或值对象的自然组成部分。
1. The operation relates to a domain concept that is not a natural part of an ENTITY or VALUE OBJECT.
2.接口是根据领域模型的其他元素定义的。
2. The interface is defined in terms of other elements of the domain model.
3.该操作是无状态的。
3. The operation is stateless.
此处的无状态性意味着任何客户端都可以使用特定服务的任何实例,而无需考虑该实例的历史记录。服务的执行将使用可访问的信息。全局性的,甚至可能改变全局信息(也就是说,它可能会产生副作用)。但与大多数领域对象不同, SERVICE本身并不持有影响其自身行为的状态。
Statelessness here means that any client can use any instance of a particular SERVICE without regard to the instance’s individual history. The execution of a SERVICE will use information that is accessible globally, and may even change that global information (that is, it may have side effects). But the SERVICE does not hold state of its own that affects its own behavior, as most domain objects do.
当领域中的重要流程或转换并非实体或值对象的固有职责时,应将该操作作为独立接口添加到模型中,并将其声明为服务。使用模型语言定义该接口,并确保操作名称属于通用语言。使该服务为无状态的。
When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless.
这种模式侧重于那些在领域内本身具有重要意义的服务,但服务当然并非仅用于领域层。它着重区分属于领域层的服务与其他层的服务,并通过职责划分来保持这种区分的清晰性。
This pattern is focused on those SERVICES that have an important meaning in the domain in their own right, but of course SERVICES are not used only in the domain layer. It takes care to distinguish SERVICES that belong to the domain layer from those of other layers, and to factor responsibilities to keep that distinction sharp.
文献中讨论的大多数服务都是纯粹的技术性的,属于基础设施层。领域服务和应用服务与这些基础设施服务协同工作。例如,一家银行可能有一个应用程序,当账户余额低于特定阈值时,该应用程序会向客户发送电子邮件。封装电子邮件系统(以及其他可能的通知方式)的接口就是基础设施层中的一个服务。
Most SERVICES discussed in the literature are purely technical and belong in the infrastructure layer. Domain and application SERVICES collaborate with these infrastructure SERVICES. For example, a bank might have an application that sends an e-mail to a customer when an account balance falls below a specific threshold. The interface that encapsulates the e-mail system, and perhaps alternate means of notification, is a SERVICE in the infrastructure layer.
区分应用层服务和领域层服务可能比较困难。应用层负责对通知进行排序。领域层负责判断是否达到阈值——尽管这项任务可能不需要服务,因为它更符合“账户”对象的职责。例如,银行应用程序可能负责资金转账。如果设计了一个服务来对资金转账进行相应的借贷操作,那么这项功能就属于领域层。资金转账在银行领域语言中具有特定的含义,并且涉及基本的业务逻辑。技术服务则完全不包含任何业务含义。
It can be harder to distinguish application SERVICES from domain SERVICES. The application layer is responsible for ordering the notification. The domain layer is responsible for determining if a threshold was met—though this task probably does not call for a SERVICE, because it would fit the responsibility of an “account” object. That banking application could be responsible for funds transfers. If a SERVICE were devised to make appropriate debits and credits for a funds transfer, that capability would belong in the domain layer. Funds transfer has a meaning in the banking domain language, and it involves fundamental business logic. Technical SERVICES should lack any business meaning at all.
许多领域或应用程序服务都构建在实体和值集合之上,它们就像脚本一样,组织领域的潜在能力以实际完成任务。实体和值对象 这些定义往往过于细粒度,无法方便地访问领域层的功能。这里,领域层和应用层之间的界限非常模糊。例如,如果银行应用程序可以将我们的交易记录转换并导出到电子表格文件供我们分析,那么这种导出操作就是一个应用服务。在银行业务领域,“文件格式”的概念并不存在,也不涉及任何业务规则。
Many domain or application SERVICES are built on top of the populations of ENTITIES and VALUES, behaving like scripts that organize the potential of the domain to actually get something done. ENTITIES and VALUE OBJECTS are often too fine-grained to provide a convenient access to the capabilities of the domain layer. Here we encounter a very fine line between the domain layer and the application layer. For example, if the banking application can convert and export our transactions into a spreadsheet file for us to analyze, that export is an application SERVICE. There is no meaning of “file formats” in the domain of banking, and there are no business rules involved.
另一方面,能够将资金从一个账户转移到另一个账户的功能属于服务(SERVICE)领域,因为它嵌入了重要的业务规则(例如,贷记和借记相应的账户),而且“资金转移”本身就是一个有意义的银行术语。在这种情况下,服务本身并不执行太多操作;它会请求两个账户对象来完成大部分工作。但是,如果将“转移”操作直接放在账户对象上,那就显得很不方便,因为该操作涉及两个账户和一些全局规则。
On the other hand, a feature that can transfer funds from one account to another is a domain SERVICE because it embeds significant business rules (crediting and debiting the appropriate accounts, for example) and because a “funds transfer” is a meaningful banking term. In this case, the SERVICE does not do much on its own; it would ask the two Account objects to do most of the work. But to put the “transfer” operation on the Account object would be awkward, because the operation involves two accounts and some global rules.
我们可能需要创建一个资金转账对象来表示两条记录以及转账规则和历史记录。但我们仍然需要调用银行间网络中的服务。此外,在大多数开发系统中,直接在领域对象和外部资源之间建立接口非常麻烦。我们可以用一个外观(FACADE)来包装这些外部服务,该外观接受模型形式的输入,并可能返回一个资金转账对象作为结果。但是,无论我们使用哪些中间机构,即使它们不属于我们,这些服务仍然在履行资金转账的领域职责。
We might like to create a Funds Transfer object to represent the two entries plus the rules and history around the transfer. But we are still left with calls to SERVICES in the interbank networks. What’s more, in most development systems, it is awkward to make a direct interface between a domain object and external resources. We can dress up such external SERVICES with a FACADE that takes inputs in terms of the model, perhaps returning a Funds Transfer object as its result. But whatever intermediaries we might have, and even though they don’t belong to us, those SERVICES are carrying out the domain responsibility of funds transfer.
将服务划分为多个层
Partitioning Services into Layers
虽然这种模式讨论强调了将概念建模为服务的表达能力,但该模式作为控制领域层接口粒度以及将客户端与实体和值对象解耦的手段也很有价值。
Although this pattern discussion has emphasized the expressiveness of modeling a concept as a SERVICE, the pattern is also valuable as a means of controlling granularity in the interfaces of the domain layer, as well as decoupling clients from the ENTITIES and VALUE OBJECTS.
中等粒度、无状态的服务更容易在大型系统中复用,因为它们将重要的功能封装在一个简单的接口背后。此外,细粒度的对象可能会导致分布式系统中消息传递效率低下。
Medium-grained, stateless SERVICES can be easier to reuse in large systems because they encapsulate significant functionality behind a simple interface. Also, fine-grained objects can lead to inefficient messaging in a distributed system.
如前所述,细粒度的领域对象可能会导致领域知识泄漏到应用层,因为领域对象的行为正是在应用层进行协调的。高度精细的交互的复杂性最终由应用层处理,这使得领域知识悄然渗入应用程序或用户界面代码,从而与领域层脱节。合理引入领域服务有助于保持各层之间的清晰界限。
As previously discussed, fine-grained domain objects can contribute to knowledge leaks from the domain into the application layer, where the domain object’s behavior is coordinated. The complexity of a highly detailed interaction ends up being handled in the application layer, allowing domain knowledge to creep into the application or user interface code, where it is lost from the domain layer. The judicious introduction of domain services can help maintain the bright line between layers.
这种模式优先考虑界面简洁性,而非客户端控制和多功能性。它提供适中的功能粒度,非常适合打包大型或分布式系统的组件。有时,服务(SERVICE)是表达领域概念最自然的方式。
This pattern favors interface simplicity over client control and versatility. It provides a medium grain of functionality very useful in packaging components of large or distributed systems. And sometimes a SERVICE is the most natural way to express a domain concept.
分布式系统架构,例如 J2EE 和 CORBA,为服务提供了特殊的发布机制和使用规范,并增加了分发和访问功能。但并非所有项目都会使用这类框架,即使使用了,如果目的仅仅是为了逻辑上分离关注点,那么使用这类框架也可能显得过于复杂。
Distributed system architectures, such as J2EE and CORBA, provide special publishing mechanisms for SERVICES, with conventions for their use, and they add distribution and access capabilities. But such frameworks are not always in use on a project, and even when they are, they are likely to be overkill when the motivation is just a logical separation of concerns.
提供服务的访问方式并不像划分具体职责的设计决策那样重要。“执行者”对象可以作为服务接口的实现。编写一个简单的单例模式(Gamma 等人,1995)即可轻松提供访问。编码规范可以明确表明这些对象只是服务接口的交付机制,而非有意义的领域对象。只有当真正需要分布式部署系统或以其他方式利用框架功能时,才应使用复杂的架构。
The means of providing access to a SERVICE is not as important as the design decision to carve off specific responsibilities. A “doer” object may be satisfactory as an implementation of a SERVICE’s interface. A simple SINGLETON (Gamma et al. 1995) can be written easily to provide access. Coding conventions can make it clear that these objects are just delivery mechanisms for SERVICE interfaces, and not meaningful domain objects. Elaborate architectures should be used only when there is a real need to distribute the system or otherwise draw on the framework’s capabilities.
模块化是一种历史悠久且成熟的设计元素。虽然存在技术方面的考量,但模块化的主要驱动力在于避免认知过载。模块化为人们提供了两种查看模型的方式:他们可以查看模块内部的细节而不被整体信息所淹没,也可以查看模块之间的关系,而无需查看内部细节。
MODULES are an old, established design element. There are technical considerations, but cognitive overload is the primary motivation for modularity. MODULES give people two views of the model: They can look at detail within a MODULE without being overwhelmed by the whole, or they can look at relationships between MODULES in views that exclude interior detail.
领域层中的模块应该成为模型中有意义的一部分,从更大的层面讲述领域的故事。
The MODULES in the domain layer should emerge as a meaningful part of the model, telling the story of the domain on a larger scale.
每个人都在使用模块,但很少有人将其视为模型中不可或缺的一部分。代码被分解成各种各样的类别,从技术架构的各个方面到开发人员的工作分配。即使是经常重构的开发人员,也往往满足于项目早期设计的模块。
Everyone uses MODULES, but few treat them as a full-fledged part of the model. Code gets broken down into all sorts of categories, from aspects of the technical architecture to developers’ work assignments. Even developers who refactor a lot tend to content themselves with MODULES conceived early in the project.
模块间低耦合、模块内高内聚是常识。对耦合和内聚的解释往往将其视为技术指标,需要根据关联和交互的分布情况进行机械式的评判。然而,划分模块的不仅仅是代码,还有概念。一个人同时能够思考的事情是有限的(因此需要低耦合)。不连贯的碎片化想法就像一锅未经区分的杂乱无章的想法一样难以理解(因此需要高内聚)。
It is a truism that there should be low coupling between MODULES and high cohesion within them. Explanations of coupling and cohesion tend to make them sound like technical metrics, to be judged mechanically based on the distributions of associations and interactions. Yet it isn’t just code being divided into MODULES, but concepts. There is a limit to how many things a person can think about at once (hence low coupling). Incoherent fragments of ideas are as hard to understand as an undifferentiated soup of ideas (hence high cohesion).
低耦合和高内聚是通用的设计原则,既适用于单个对象,也适用于模块,但在建模和设计的大尺度层面上尤为重要。这些术语由来已久;Larman 1998 年的著作中就提供了一种模式式的解释。
Low coupling and high cohesion are general design principles that apply as much to individual objects as to MODULES, but they are particularly important at this larger grain of modeling and design. These terms have been around for a long time; one patterns-style explanation can be found in Larman 1998.
当两个模型元素被分离到不同的模块中时,它们之间的关系会变得不如以前直接,这会增加理解它们在设计中位置的难度。模块间的低耦合性可以最大限度地降低这种成本,并使得在分析一个模块的内容时,只需参考其他相互作用的模块即可。
Whenever two model elements are separated into different modules, the relationships between them become less direct than they were, which increases the overhead of understanding their place in the design. Low coupling between MODULES minimizes this cost, and makes it possible to analyze the contents of one MODULE with a minimum of reference to others that interact.
同时,一个优秀模型的各个要素之间具有协同效应,精心选择的模块能够将模型中概念关系尤为丰富的要素整合在一起。这种具有相关职责的对象之间的高度凝聚力,使得建模和设计工作能够集中在一个模块内进行,这种复杂程度是人类思维能够轻松应对的。
At the same time, the elements of a good model have synergy, and well-chosen MODULES bring together elements of the model with particularly rich conceptual relationships. This high cohesion of objects with related responsibilities allows modeling and design work to concentrate within a single MODULE, a scale of complexity a human mind can easily handle.
模块及其下属元素本应协同演化,但通常并非如此。选择模块是为了组织对象的早期形态。之后,对象往往会发生变化,但这些变化仍然会限制在现有模块定义的范围内。重构模块比重构类更费力,也更具破坏性,因此频率可能不高。但正如模型对象往往从简单具体开始,然后逐渐演变以展现更深层次的洞察一样,模块也可以变得微妙抽象。让模块反映对领域理解的不断变化,也能让模块内的对象拥有更大的演化自由。
MODULES and the smaller elements should coevolve, but typically they do not. MODULES are chosen to organize an early form of the objects. After that, the objects tend to change in ways that keep them in the bounds of the existing MODULE definition. Refactoring MODULES is more work and more disruptive than refactoring classes, and probably can’t be as frequent. But just as model objects tend to start out naive and concrete and then gradually transform to reveal deeper insight, MODULES can become subtle and abstract. Letting the MODULES reflect changing understanding of the domain will also allow more freedom for the objects within them to evolve.
就像领域驱动设计中的其他一切一样,模块也是一种沟通机制。被划分的对象的含义需要决定模块的选择。当你把一些类放在一个模块中时,你就是在告诉下一个查看你设计的开发人员,要把它们放在一起考虑。如果你的模型在讲述一个故事,那么模块就是故事的章节。模块的名称传达了它的含义。这些名称会进入通用语言。“现在我们来谈谈‘客户’模块,”你可能会对一位业务专家这样说,这样就为你们的对话设定了上下文。
Like everything else in a domain-driven design, MODULES are a communications mechanism. The meaning of the objects being partitioned needs to drive the choice of MODULES. When you place some classes together in a MODULE, you are telling the next developer who looks at your design to think about them together. If your model is telling a story, the MODULES are chapters. The name of the MODULE conveys its meaning. These names enter the UBIQUITOUS LANGUAGE. “Now let’s talk about the ‘customer’ module,” you might say to a business expert, and the context is set for your conversation.
所以:
Therefore:
选择能够讲述系统故事并包含一套内聚概念的模块。这通常会降低模块间的耦合度,但如果并非如此,则需要寻找方法来修改模型以解耦概念,或者寻找可能被忽略的概念,这些概念或许可以作为模块的基础,从而以有意义的方式将各个元素整合在一起。追求低耦合度,指的是概念之间可以相互独立地理解和推理。不断完善模型,直到它能够根据高层领域概念进行划分,并且相应的代码也实现了解耦。
Choose MODULES that tell the story of the system and contain a cohesive set of concepts. This often yields low coupling between MODULES, but if it doesn’t, look for a way to change the model to disentangle the concepts, or search for an overlooked concept that might be the basis of a MODULE that would bring the elements together in a meaningful way. Seek low coupling in the sense of concepts that can be understood and reasoned about independently of each other. Refine the model until it partitions according to high-level domain concepts and the corresponding code is decoupled as well.
为构成通用语言的模块命名。模块及其名称应体现对该领域的深刻理解。
Give the MODULES names that become part of the UBIQUITOUS LANGUAGE. MODULES and their names should reflect insight into the domain.
考察概念关系并非技术措施的替代方案。它们是同一问题的不同层面,两者都必须兼顾。但以模型为中心的思考方式能够产生更深层次的解决方案,而非权宜之计。当必须做出取舍时,最好选择概念清晰的方案,即便这意味着模块之间需要更多引用,或者在模块变更时会产生一些连锁反应。如果开发人员理解模型所传达的信息,他们就能应对这些问题。
Looking at conceptual relationships is not an alternative to technical measures. They are different levels of the same issue, and both have to be accomplished. But model-focused thinking produces a deeper solution, rather than an incidental one. And when there has to be a trade-off, it is best to go with the conceptual clarity, even if it means more references between MODULES or occasional ripple effects when changes are made to a MODULE. Developers can handle these problems if they understand the story the model is telling them.
模块需要与模型的其他部分协同演进。这意味着模块需要与模型和代码同步重构。但这种重构往往难以实现。模块的变更通常需要对代码进行大范围的更新。此类变更可能会扰乱团队沟通,甚至会给开发工具(例如源代码控制系统)带来麻烦。因此,模块的结构和名称通常反映的是比类更早期的模型形式。
MODULES need to coevolve with the rest of the model. This means refactoring MODULES right along with the model and code. But this refactoring often doesn’t happen. Changing MODULES tends to require widespread updates to the code. Such changes can be disruptive to team communication and can even throw a monkey wrench into development tools, such as source code control systems. As a result, MODULE structures and names often reflect much earlier forms of the model than the classes do.
模块选择上的早期错误不可避免地会导致高度耦合,这使得重构变得困难。缺乏重构只会不断加剧这种惰性。克服这一问题的唯一方法是咬紧牙关,根据经验找出问题所在,并重新组织模块。
Inevitable early mistakes in MODULE choices lead to high coupling, which makes it hard to refactor. The lack of refactoring just keeps increasing the inertia. It can only be overcome by biting the bullet and reorganizing MODULES based on experience of where the trouble spots lie.
某些开发工具和编程系统会加剧这个问题。无论最终采用何种开发技术,我们都需要寻找方法来最大限度地减少模块重构的工作量,并最大限度地减少与其他开发人员沟通时的混乱。
Some development tools and programming systems exacerbate the problem. Whatever development technology the implementation will be based on, we need to look for ways of minimizing the work of refactoring MODULES, and minimizing clutter in communicating to other developers.
在 Java 中,导入(依赖关系)必须在某个单独的类中声明。建模者可能会将包视为依赖于其他包。虽然可以使用包,但这在 Java 中无法直接表达。常见的编码规范鼓励导入特定的类,从而生成类似这样的代码:
In Java, imports (dependencies) must be declared in some individual class. A modeler probably thinks of packages as depending on other packages, but this can’t be stated in Java. Common coding conventions encourage the import of specific classes, resulting in code like this:
ClassA1
import packageB.ClassB1;
import packageB.ClassB2;
import packageB.ClassB3;
import packageC.ClassC1;
import packageC.ClassC2;
import packageC.ClassC3;
. . .
在 Java 中,很遗憾,无法避免将包导入到各个类中,但至少可以一次导入整个包,这体现了包是高度内聚单元的意图,同时也减少了更改包名称的工作量。
In Java, unfortunately, there is no escape from importing into individual classes, but you can at least import entire packages at a time, reflecting the intention that packages are highly cohesive units while simultaneously reducing the effort of changing package names.
ClassA1
import packageB.*;
import packageC.*;
. . .
没错,这种技术意味着混合两种规模(类依赖于包),但它传达的信息比之前大量的类列表更多——它传达了创建对特定模块的依赖性的意图。
True, this technique means mixing two scales (classes depend on packages), but it communicates more than the previous voluminous list of classes—it conveys the intent to create a dependency on particular MODULES.
如果一个类确实依赖于另一个包中的特定类,并且本地模块似乎没有对另一个模块的概念依赖性,那么也许应该移动该类,或者应该重新考虑模块本身。
If an individual class really does depend on a specific class in another package, and the local MODULE doesn’t seem to have a conceptual dependency on the other MODULE, then maybe a class should be moved, or the MODULES themselves should be reconsidered.
技术框架对我们的包装决策有着强大的影响力。其中一些是有益的,而另一些则需要抵制。
Strong forces on our packaging decisions come from technical frameworks. Some of these are helpful, while others need to be resisted.
一个非常有用的框架标准的例子是强制执行分层架构,即将基础设施和用户界面代码放入单独的包组中,使领域层在物理上分离成自己的一组包。
An example of a very useful framework standard is the enforcement of LAYERED ARCHITECTURE by placing infrastructure and user interface code into separate groups of packages, leaving the domain layer physically separated into its own set of packages.
另一方面,分层架构可能会导致模型对象的实现碎片化。一些框架通过将单个领域对象的职责分散到多个对象,然后将这些对象放在不同的包中来创建分层。例如,在 J2EE 中,一种常见的做法是将数据和数据访问放在“实体 Bean”中,而将相关的业务逻辑放在“会话 Bean”中。除了增加每个组件的实现复杂性之外,这种分离还会立即破坏对象模型的内聚性。对象最基本的概念之一是将数据与操作该数据的逻辑封装在一起。这种分层实现并非致命,因为这两个组件可以被视为共同构成单个模型元素的实现,但更糟糕的是,实体 Bean 和会话 Bean 通常被分离到不同的包中。此时,要查看各种对象并将它们重新组合成一个单一的概念实体就太费力了。我们失去了模型和设计之间的联系。最佳实践是使用比实体对象粒度更大的 EJB,这样可以减少分层带来的弊端。但即使是细粒度的对象,也常常会被拆分成多个层。
On the other hand, tiered architectures can fragment the implementation of the model objects. Some frameworks create tiers by spreading the responsibilities of a single domain object across multiple objects and then placing those objects in separate packages. For example, with J2EE a common practice is to place data and data access into an “entity bean” while placing associated business logic into a “session bean.” In addition to the increased implementation complexity of each component, the separation immediately robs an object model of cohesion. One of the most fundamental concepts of objects is to encapsulate data with the logic that operates on that data. This kind of tiered implementation is not fatal, because both components can be viewed as together constituting the implementation of a single model element, but to make matters worse, the entity and session beans are often separated into different packages. At that point, viewing the various objects and mentally fitting them back together as a single conceptual ENTITY is just too much effort. We lose the connection between the model and design. Best practice is to use EJBs at a larger grain than ENTITY objects, reducing the downside of separating tiers. But fine-grain objects are often split into tiers also.
例如,我在一个管理得相当合理的项目中遇到了这些问题。在这个项目中,每个概念对象实际上都被拆分成了四个层级,每个层级都有其合理的划分依据。第一层是数据持久层,负责关系数据库的映射和访问。第二层处理对象在所有情况下固有的行为。第三层用于叠加特定于应用程序的功能。第四层则是一个公共接口,与所有底层实现解耦。这种方案略显复杂,但各层定义清晰,关注点分离也颇为简洁。我们原本可以接受将所有物理对象都理解为一个概念对象。有时,这种方面分离甚至有所帮助。特别是,将持久化代码移出底层,大大减少了代码的冗余。
For example, I encountered these problems on a rather intelligently run project in which each conceptual object was actually broken into four tiers. Each division had a good rationale. The first tier was a data persistence layer, handling mapping and access to the relational database. Then came a layer that handled behavior intrinsic to the object in all situations. Next was a layer for superimposing application-specific functionality. The fourth tier was meant as a public interface, decoupled from all the implementation below. This scheme was a bit too complicated, but the layers were well defined and there was some tidiness to the separation of concerns. We could have lived with mentally connecting all the physical objects making up one conceptual object. The separation of aspects even helped at times. In particular, having the persistence code moved out removed a lot of clutter.
但除此之外,该框架还要求每个层级都位于一组独立的包中,并按照一套能够识别层级的命名约定进行命名。这占据了所有用于划分的思维空间。因此,领域开发人员往往避免创建过多的模块(每个模块又被乘以四),并且几乎从不修改模块,因为重构模块的工作量实在太大。更糟糕的是,要找到定义单个概念类的所有数据和行为非常困难(再加上分层结构的间接性),以至于开发人员几乎没有精力去思考模型。应用程序最终交付了,但其领域模型却十分贫乏,基本上只满足了应用程序的数据库访问需求,行为则由少数几个服务提供。本应从模型驱动设计中获得的优势却被限制住了,因为代码没有透明地展现模型,开发人员无法直接使用它。
But on top of all this, the framework required each tier to be in a separate set of packages, named according to a convention that identified the tier. This took up all the mental room for partitioning. As a result, domain developers tended to avoid making too many MODULES (each of which was multiplied by four) and hardly ever changed one, because the effort of refactoring a MODULE was prohibitive. Worse, hunting down all the data and behavior that defined a single conceptual class was so difficult (combined with the indirectness of the layering) that developers didn’t have much mental space left to think about models. The application was delivered, but with an anemic domain model that basically fulfilled the database access requirements of the application, with behavior supplied by a few SERVICES. The leverage that should have derived from MODEL-DRIVEN DESIGN was limited because the code did not transparently reveal the model and allow a developer to work with it.
这种框架设计试图解决两个合理的问题。一是逻辑上的职责划分:一个对象负责数据库访问,另一个负责业务逻辑,以此类推。这种划分使得每一层(在技术层面)的功能更容易理解,也便于层与层之间的切换。问题在于,它忽略了对应用程序开发的成本。本书并非框架设计专著,因此我不会深入探讨解决该问题的替代方案,但这些方案确实存在。即便没有其他选择,为了获得更统一的领域层,牺牲一些优势也是更好的选择。
This kind of framework design is attempting to address two legitimate issues. One is the logical division of concerns: One object has responsibility for database access, another for business logic, and so on. Such divisions make it easier to understand the functioning of each tier (on a technical level) and make it easier to switch out layers. The trouble is that the cost to application development is not recognized. This is not a book on framework design, so I won’t go into alternative solutions to that problem, but they do exist. And even if there were no options, it would be better to trade off these benefits for a more cohesive domain layer.
这些打包方案的另一个动机是分层部署。如果代码真的部署在不同的服务器上,这或许是一个强有力的论据。但通常情况下并非如此。这种灵活性仅仅是为了以防万一。对于一个希望利用模型驱动设计的项目而言,除非能解决迫在眉睫的问题,否则这种牺牲就太大了。
The other motivation for these packaging schemes is the distribution of tiers. This could be a strong argument if the code actually got deployed on different servers. Usually it does not. The flexibility is sought just in case it is needed. On a project that hopes to get leverage from MODEL-DRIVEN DESIGN, this sacrifice is too great unless it solves an immediate and pressing problem.
精心设计的、技术驱动的包装方案会带来两项成本。
Elaborate technically driven packaging schemes impose two costs.
• 如果框架的划分约定将实现概念对象的元素分开,则代码将不再揭示模型。
• If the framework’s partitioning conventions pull apart the elements implementing the conceptual objects, the code no longer reveals the model.
• 人的思维能够将有限的分割空间重新整合起来,如果框架用尽了所有空间,领域开发人员就会失去将模型分割成有意义的部分的能力。
• There is only so much partitioning a mind can stitch back together, and if the framework uses it all up, the domain developers lose their ability to chunk the model into meaningful pieces.
最好保持简单。选择最少的技术分区规则,这些规则对于技术环境至关重要,或者确实有助于开发。例如,解耦复杂数据从对象的行为层面来看,持久化代码可能使重构更容易。
It is best to keep things simple. Choose a minimum of technical partitioning rules that are essential to the technical environment or actually aid development. For example, decoupling complicated data persistence code from the behavioral aspects of the objects may make refactoring easier.
除非真心打算将代码分发到不同的服务器上,否则请将实现单个概念对象的所有代码保存在同一个模块中,如果不是同一个对象的话。
Unless there is a real intention to distribute code on different servers, keep all the code that implements a single conceptual object in the same MODULE, if not the same object.
我们原本也可以沿用“高内聚/低耦合”的传统标准,得出同样的结论。然而,实现业务逻辑的“对象”与负责数据库访问的对象之间的联系非常广泛,因此耦合度非常高。
We could have come to the same conclusion by drawing on the old standard, “high cohesion/low coupling.” The connections between an “object” implementing the business logic and the one responsible for database access are so extensive that the coupling is very high.
框架设计或公司/项目的惯例也可能存在其他陷阱,例如模糊领域对象的自然内聚性,从而破坏模型驱动设计,但最终结果是一样的。这些限制,或者仅仅是所需的大量软件包,就排除了使用其他针对领域模型需求量身定制的打包方案的可能性。
There are other pitfalls where framework design or just conventions of a company or project can undermine MODEL-DRIVEN DESIGN by obscuring the natural cohesion of the domain objects, but the bottom line is the same. The restrictions, or just the large number of required packages, rules out the use of other packaging schemes that are tailored to the needs of the domain model.
使用打包方式将领域层与其他代码分离。否则,应尽可能给予领域开发人员最大的自由度,让他们以支持自身模型和设计选择的方式来打包领域对象。
Use packaging to separate the domain layer from other code. Otherwise, leave as much freedom as possible to the domain developers to package the domain objects in ways that support their model and design choices.
但有一种例外情况,即当代码是基于声明式设计生成的(详见第 10 章)。在这种情况下,开发人员无需阅读代码,最好将其放在单独的包中,以免妨碍开发人员实际使用的设计元素。
One exception arises when code is generated based on a declarative design (discussed in Chapter 10). In that case, the developers do not need to read the code, and it is better to put it into a separate package so that it is out of the way, not cluttering up the design elements developers actually have to work with.
随着设计规模的扩大和复杂性的增加,模块化变得愈发重要。本节将介绍一些基本考虑因素。第四部分“战略设计”主要探讨了如何对大型模型和设计进行包装和分解,以及如何为人们提供理解的重点。
Modularity becomes more critical as the design gets bigger and more complex. This section presents the basic considerations. Much of Part IV, “Strategic Design,” provides approaches to packaging and breaking down big models and designs, and ways to give people focal points to guide understanding.
领域模型中的每个概念都应在实现元素中得到体现。实体、值对象及其关联,以及一些领域服务和组织模块,是实现与模型之间直接对应的点。实现中的对象、指针和检索机制必须映射到模型元素。直接上手,显而易见。如果他们不这样做,那就清理代码,修改模型,或者两者都做。
Each concept from the domain model should be reflected in an element of implementation. The ENTITIES, VALUE OBJECTS, and their associations, along with a few domain SERVICES and the organizing MODULES, are points of direct correspondence between the implementation and the model. The objects, pointers, and retrieval mechanisms in the implementation must map to model elements straightforwardly, obviously. If they do not, clean up the code, go back and change the model, or both.
切勿将任何与它们所代表的概念无关的内容添加到领域对象中。这些设计元素有其自身的职责:它们表达模型。为了使系统正常运行,还需要履行其他领域相关的职责并管理其他数据,但这些职责和数据不应包含在这些对象中。在第 6 章中,我将讨论一些支持领域层技术职责的辅助对象,例如定义数据库搜索和封装复杂的对象创建过程。
Resist the temptation to add anything to the domain objects that does not closely relate to the concepts they represent. These design elements have their job to do: they express the model. There are other domain-related responsibilities that must be carried out and other data that must be managed in order to make the system work, but they don’t belong in these objects. In Chapter 6, I will discuss some supporting objects that fulfill the technical responsibilities of the domain layer, such as defining database searches and encapsulating complex object creation.
本章介绍的四种模式为对象模型提供了构建基础。但模型驱动设计并非意味着要将所有事物都强行套入对象框架。还有其他模型范式,例如规则引擎等工具也支持这些范式。项目需要在这些范式之间做出务实的权衡。这些工具和技术是实现模型驱动设计的手段,而非替代方案。
The four patterns in this chapter provide the building blocks for an object model. But MODEL-DRIVEN DESIGN does not necessarily mean forcing everything into an object mold. There are also other model paradigms supported by tools, such as rules engines. Projects have to make pragmatic trade-offs between them. These other tools and techniques are means to the end of a MODEL-DRIVEN DESIGN, not alternatives to it.
模型驱动设计要求采用与特定建模范式相匹配的实现技术。许多建模范式都经过了实验,但只有少数几种在实践中得到广泛应用。目前,面向对象设计是主流范式,如今大多数复杂项目都采用面向对象的方式。这种主流地位的形成有多种原因:有些因素是面向对象固有的,有些是环境因素,还有一些则源于广泛应用本身带来的优势。
MODEL-DRIVEN DESIGN calls for an implementation technology in tune with the particular modeling paradigm being applied. Many such paradigms have been experimented with, but only a few have been widely used in practice. At present, the dominant paradigm is object-oriented design, and most complex projects these days set out to use objects. This predominance has come about for a variety of reasons: some factors are intrinsic to objects, some are circumstantial, and others derive from the advantages that come from wide usage itself.
团队选择对象范式的很多原因并非技术性的,甚至也不是对象本身固有的。但对象建模从一开始就很好地兼顾了简洁性和复杂性。
Many of the reasons teams choose the object paradigm are not technical, or even intrinsic to objects. But right out of the gate, object modeling does strike a nice balance of simplicity and sophistication.
如果建模范式过于深奥,那么掌握它的开发人员就会寥寥无几,最终导致他们使用不当。如果团队中的非技术成员连该范式的基本原理都无法理解,他们就无法理解模型,而通用语言(UBIQUITOUS LANGUAGE )也将难以发挥作用。可能会迷失方向。面向对象设计的基本原理对大多数人来说似乎与生俱来。尽管有些开发人员会忽略建模的细微之处,但即使是非技术人员也能看懂对象模型图。
If a modeling paradigm is too esoteric, not enough developers will master it, and they will use it badly. If the nontechnical members of the team can’t grasp at least the rudiments of the paradigm, they will not understand the model, and the UBIQUITOUS LANGUAGE will be lost. The fundamentals of object-oriented design seem to come naturally to most people. Although some developers miss the subtleties of modeling, even nontechnologists can follow a diagram of an object model.
然而,对象建模的概念虽然简单,却已足够丰富,能够捕捉重要的领域知识。而且,从一开始就有开发工具为其提供支持,使模型能够以软件的形式表达出来。
Yet, simple as the concept of object modeling is, it has proven rich enough to capture important domain knowledge. And it has been supported from the outset by development tools that allowed a model to be expressed in software.
如今,面向对象范式凭借其成熟度和广泛应用,也拥有一些显著的外部优势。如果没有成熟的基础设施和工具支持,项目可能会陷入技术研发的泥潭,导致资源从应用开发中转移,延误开发进度,并引入技术风险。某些技术彼此兼容性不佳,可能无法与行业标准解决方案集成,迫使团队重新发明一些常用工具。但多年来,许多此类问题已针对面向对象范式得到解决,或因其广泛应用而变得无关紧要。(现在,如何与主流面向对象技术集成,则取决于其他方法。)大多数新技术都提供了与流行的面向对象平台集成的途径。这使得集成更加便捷,甚至允许混合使用基于其他建模范式的子系统(我们将在本章后面讨论这一点)。
Today, the object paradigm also has some significant circumstantial advantages deriving from maturity and widespread adoption. Without mature infrastructure and tool support, a project can get sidetracked into technological R&D, delaying and diverting resources away from application development and introducing technical risks. Some technologies don’t play well with others, and it may not be possible to integrate them with industry-standard solutions, forcing the team to reinvent common utilities. But over the years, many of these problems have been solved for objects, or made irrelevant by widespread adoption. (Now it falls on other approaches to integrate with mainstream object technology.) Most new technologies provide the means to integrate with the popular object-oriented platforms. This makes integration easier and even leaves open the option of mixing in subsystems based on other modeling paradigms (which we will discuss later in this chapter).
同样重要的是开发者社区的成熟度和设计文化本身。一个采用全新范式的项目可能找不到精通该技术的开发者,或者找不到有经验在该范式下创建有效模型的开发者。由于充分利用该范式和技术的模式尚未完全成熟,因此在合理的时间内培训开发者可能并不现实。或许该领域的先驱者们卓有成效,但尚未以易于理解的方式公开他们的见解。
Equally important is the maturity of the developer community and the design culture itself. A project that adopts a novel paradigm may be unable to find developers with expertise in the technology, or with the experience to create effective models in the chosen paradigm. It may not be feasible to educate developers in a reasonable amount of time because the patterns for making the most of the paradigm and technology haven’t gelled yet. Perhaps the pioneers of the field are effective but haven’t yet published their insights in an accessible form.
对象已被成千上万的开发人员、项目经理以及参与项目工作的所有其他专家所理解。
Objects are already understood by a community of thousands of developers, project managers, and all the other specialists involved in project work.
十年前一个面向对象项目的故事,生动地展现了在不成熟的范式下工作的风险。上世纪90年代初,这个项目致力于采用多项前沿技术,包括大规模使用面向对象数据库。这令人兴奋不已。团队成员会自豪地告诉来访者,我们正在部署这项技术所支持的最大数据库。我加入项目时,各个团队都在轻松地开发面向对象设计,并将他们的对象存储到数据库中。但渐渐地,我们意识到,我们竟然开始占用数据库相当大的容量——而这仅仅是测试数据!实际数据库的规模将是现在的几十倍。实际事务量也将是现在的几十倍。难道这项技术真的无法应用于这个应用吗?还是我们使用不当?我们显然力不从心。
A story from an object-oriented project of only a decade ago illustrates the risks of working in an immature paradigm. In the early 1990s, this project committed itself to several cutting-edge technologies, including use of an object-oriented database on a large scale. It was exciting. People on the team would proudly tell visitors that we were deploying the biggest database this technology had ever supported. When I joined the project, different teams were spinning out object-oriented designs and storing their objects in the database effortlessly. But gradually the realization crept upon us that we were beginning to absorb a significant fraction of the database’s capacity—with test data! The actual database would be dozens of times larger. The actual transaction volume would be dozens of times higher. Was it impossible to use this technology for this application? Had we used it improperly? We were out of our depth.
幸运的是,我们聘请到了世界上为数不多的几位拥有解决此问题所需技能的人才之一。他开出了价码,我们也照付不误。问题出在三个方面。首先,数据库自带的现成基础设施根本无法满足我们的需求。其次,存储细粒度对象的成本远超我们的预期。第三,对象模型的某些部分存在错综复杂的相互依赖关系,即使并发事务数量相对较少,也会出现争用问题。
Fortunately, we were able to bring onto the team one of a handful of people in the world with the skills to extricate us from the problem. He named his price and we paid it. There were three sources of the problem. First, the off-the-shelf infrastructure provided with the database simply didn’t scale up to our needs. Second, storage of fine-grained objects turned out to be much more costly than we had realized. Third, parts of the object model had such a tangle of interdependencies that contention became a problem with a relatively small number of concurrent transactions.
在聘请的专家的帮助下,我们改进了基础设施。团队现在意识到细粒度对象的影响,开始寻找更适合这项技术的模型。我们所有人都加深了对限制模型中关系网络重要性的思考,并开始将这种新的理解应用于构建更好的模型,从而在紧密相关的聚合体之间实现更紧密的解耦。
With the help of this hired expert, we enhanced the infrastructure. The team, now aware of the impact of fine-grained objects, began to find models that worked better with this technology. All of us deepened our thinking about the importance of limiting the web of relationships in a model, and we began applying this new understanding to making better models with more decoupling between closely interrelated aggregates.
除了之前几个月走错路所浪费的时间,这次恢复又耗费了几个月的时间。而这并非团队第一次因所选技术的不成熟以及自身缺乏相关学习经验而遭遇挫折。遗憾的是,这个项目最终不得不收缩规模,变得相当保守。时至今日,他们仍然在使用那些前沿技术,但仅限于一些可能并未真正从中受益的、范围有限的应用场景。
Several months were lost in this recovery, in addition to the earlier months spent going down a failed path. And this had not been the team’s first setback resulting from the immaturity of the chosen technologies and our own lack of experience with the associated learning curve. Sadly, this project eventually retrenched and became quite conservative. To this day they use the exotic technologies, but for cautiously scoped applications that probably don’t really benefit from them.
十年后,面向对象技术已相对成熟。大多数常见的基础设施需求都可以用现成的解决方案来满足,这些方案已经在实际应用中得到验证。关键任务工具通常来自大型供应商(通常是多家供应商),或者来自稳定的开源项目。这些基础设施组件本身应用广泛,因此已经积累了一批熟悉它们的人员,并且还有相关的书籍和资料。这些成熟技术的局限性也得到了充分的了解,因此经验丰富的团队不太可能过度开发。
A decade later, object-oriented technology is relatively mature. Most common infrastructure needs can be met with off-the-shelf solutions that have been used in the field. Mission-critical tools come from major vendors, often multiple vendors, or from stable open-source projects. Many of these infrastructure pieces themselves are used widely enough that there is a base of people who already understand them, as well as books explaining them, and so forth. The limitations of these established technologies are fairly well understood, so that knowledgeable teams are less likely to overreach.
其他一些有趣的建模范式尚未达到这种成熟度。有些范式掌握难度过高,注定只能在小众领域应用。另一些范式则潜力巨大,但其技术基础架构仍不完善或不稳定,鲜有人真正理解如何为其创建优秀模型。这些范式或许终有一天会走向成熟,但目前还无法应用于大多数项目。
Other interesting modeling paradigms just don’t have this maturity. Some are too hard to master and will never be used outside small specialties. Others have potential, but the technical infrastructure is still patchy or shaky, and few people understand the subtleties of creating good models for them. These may come of age, but they are not ready for most projects.
因此,目前大多数尝试模型驱动设计的项目明智地选择以面向对象技术作为系统核心。它们不会被限制在纯对象系统中——因为对象已成为行业主流,现在已有集成工具可以与几乎所有其他现有技术连接。
This is why, for the present, most projects attempting MODEL-DRIVEN DESIGN are wise to use object-oriented technology as the core of their system. They will not be locked into an object-only system—because objects have become the mainstream of the industry, integration tools are available to connect with almost any other technology in current use.
但这并不意味着人们应该永远局限于对象。随波逐流固然能带来一定的安全感,但这并非总是最佳选择。对象模型可以解决大量实际的软件问题,但有些领域并不适合建模为离散的、封装的行为包。例如,那些高度数学化或以全局逻辑推理为主导的领域,就很难融入面向对象的范式。
Yet this doesn’t mean that people should restrict themselves to objects forever. Traveling with the crowd provides some safety, but it isn’t always the way to go. Object models address a large number of practical software problems, but there are domains that are not natural to model as discrete packets of encapsulated behavior. For example, domains that are intensely mathematical or that are dominated by global logical reasoning do not fit well into the object-oriented paradigm.
领域模型不一定是对象模型。例如,Prolog 中就实现了模型驱动设计,其模型由逻辑规则和事实构成。模型范式的出现是为了满足人们思考领域的特定方式。然后,这些领域的模型会受到范式的影响。最终得到的模型符合该范式,因此可以在支持该建模风格的工具中有效实现。
A domain model does not have to be an object model. There are MODEL-DRIVEN DESIGNS implemented in Prolog, for example, with a model made up of logical rules and facts. Model paradigms have been conceived to address certain ways people like to think about domains. Then the models of those domains are shaped by the paradigm. The result is a model that conforms to the paradigm so that it can be effectively implemented in the tools that support that modeling style.
无论项目中采用何种主导模型范式,领域中总有一些部分用其他范式更容易表达。如果领域中只有少数几个元素与其他范式不兼容,而其他部分在某种范式下都能很好地运行,那么开发者可以接受在整体一致的模型中存在一些略显笨拙的对象。(或者,在另一个极端,如果大部分问题领域用某种特定的范式表达更自然,那么完全切换范式并选择不同的实现平台可能更有意义。)但是,当领域的主要部分似乎属于不同的范式时,使用合适的范式对每个部分进行建模,并结合多种工具集来支持实现,在理论上是很有吸引力的。当相互依赖性较小时,可以将其他范式中的子系统封装起来,例如只需由对象调用的复杂数学计算。而在其他情况下,不同的方面则更加交织在一起,例如当对象的交互依赖于某些数学关系时。
Whatever the dominant model paradigm may be on a project, there are bound to be parts of the domain that would be much easier to express in some other paradigm. When there are just a few anomalous elements of a domain that otherwise works well in a paradigm, developers can live with a few awkward objects in an otherwise consistent model. (Or, on the other extreme, if the greater part of the problem domain is more naturally expressed in a particular other paradigm, it may make sense to switch paradigms altogether and choose a different implementation platform.) But when major parts of the domain seem to belong to different paradigms, it is intellectually appealing to model each part in a paradigm that fits, using a mixture of tool sets to support implementation. When the interdependence is small, a subsystem in the other paradigm can be encapsulated, such as a complex math calculation that simply needs to be called by an object. Other times the different aspects are more intertwined, such as when the interaction of the objects depends on some mathematical relationships.
正是这种理念促使我们将业务规则引擎和工作流引擎等非对象组件集成到对象系统中。混合不同的范式允许开发人员以最合适的方式对特定概念进行建模。此外,大多数系统都必须使用一些非对象技术基础设施,最常见的是关系数据库。但是,构建一个跨越不同范式的连贯模型非常困难,而让支持工具共存也十分复杂。当开发人员无法清晰地看到软件中体现的连贯模型时,模型驱动设计(MDD)就可能失效,即便这种混合反而增加了对MDD的需求。
This is what motivates the integration into object systems of such nonobject components as business rules engines and workflow engines. Mixing paradigms allows developers to model particular concepts in the style that fits best. Furthermore, most systems must use some nonobject technical infrastructure, most commonly relational databases. But making a coherent model that spans paradigms is hard, and making the supporting tools coexist is complicated. When developers can’t clearly see a coherent model embodied in the software, MODEL-DRIVEN DESIGN can go out the window, even as this mixture increases the need for it.
规则引擎将作为示例,说明有时会将一种技术融入到面向对象的应用程序开发项目中。知识丰富的领域模型可能包含明确的规则,但对象范式缺乏用于描述规则及其交互的具体语义。尽管规则可以建模为对象,而且通常也能成功建模,但对象封装使得应用跨整个系统的全局规则变得困难。规则引擎技术逻辑范式之所以吸引人,是因为它承诺提供一种更自然、更声明式的规则定义方式,从而有效地将规则范式融入到对象范式中。逻辑范式发展完善且功能强大,似乎能够很好地弥补对象范式的优势和劣势。
Rules engines will serve as an example of a technology sometimes mixed into an object-oriented application development project. A knowledge-rich domain model probably contains explicit rules, yet the object paradigm lacks specific semantics for stating rules and their interactions. Although rules can be modeled as objects, and often are successfully, object encapsulation makes it awkward to apply global rules that cross the whole system. Rules engine technology is appealing because it promises to provide a more natural and declarative way to define rules, effectively allowing the rules paradigm to be mixed into the object paradigm. The logic paradigm is well developed and powerful, and it seems like a good complement to the strengths and weaknesses of objects.
但人们并非总能从规则引擎中获得预期效果。有些产品确实不好用。有些产品缺乏无缝视图,无法展现运行于两个不同实现环境中的模型概念之间的关联性。一个常见的结果是应用程序被分割成两部分:一部分是使用对象的静态数据存储系统,另一部分是几乎完全失去与对象模型连接起来的临时规则处理应用程序。
But people don’t always get what they hope for out of rules engines. Some products just don’t work very well. Some lack a seamless view that can show the relatedness of model concepts that run between the two implementation environments. One common outcome is an application fractured in two: a static data storage system using objects, and an ad hoc rules processing application that has lost almost all connection with the object model.
在处理规则时,始终保持模型思维至关重要。团队必须找到一个能够兼容两种实现范式的单一模型。这并非易事,但如果规则引擎支持富有表现力的实现,则应该能够做到。否则,数据和规则就会脱节。引擎中的规则最终更像是小型程序,而非领域模型中的概念规则。只有当规则和对象之间建立紧密、清晰的关系时,才能确保两者含义的一致性。
It is important to continue to think in terms of models while working with rules. The team has to find a single model that can work with both implementation paradigms. This is not easy, but it should be possible if the rules engine allows expressive implementation. Otherwise, the data and the rules become unconnected. The rules in the engine end up more like little programs than conceptual rules in the domain model. With tight, clear relationships between the rules and the objects, the meaning of both pieces is retained.
如果没有一个无缝衔接的环境,开发人员就必须提炼出一个由清晰、基本概念组成的模型,将整个设计凝聚在一起。
Without a seamless environment, it falls on the developers to distill a model made up of clear, fundamental concepts to hold the whole design together.
将各个部分连接起来的最有效工具是构成整个异构模型底层基础的强大通用语言。在两个环境中一致地应用名称,并在通用语言中使用这些名称,有助于弥合两者之间的差距。
The most effective tool for holding the parts together is a robust UBIQUITOUS LANGUAGE that underlies the whole heterogeneous model. Consistently applying names in the two environments and exercising those names in the UBIQUITOUS LANGUAGE can help bridge the gap.
这是一个值得单独成书的话题。本节的目的仅仅在于说明,我们不必放弃模型驱动设计,而且坚持使用它也是值得的。
This is a topic that deserves a book of its own. The goal of this section is merely to show that it isn’t necessary to give up MODEL-DRIVEN DESIGN, and that it is worth the effort to keep it.
虽然模型驱动设计不一定是面向对象的,但它确实依赖于对模型构造(无论是对象、规则还是工作流)的清晰表达。如果现有工具无法实现这种清晰表达,则应重新考虑工具的选择。缺乏清晰表达的实现会抵消这种额外范式的优势。
Although a MODEL-DRIVEN DESIGN does not have to be object oriented, it does depend on having an expressive implementation of the model constructs, be they objects, rules, or workflows. If the available tool does not facilitate that expressiveness, reconsider the choice of tools. An unexpressive implementation negates the advantage of the extra paradigm.
Here are four rules of thumb for mixing nonobject elements into a predominantly object-oriented system:
•不要对抗既定的实现范式。总有另一种思考领域的方法。找到符合该范式的模型概念。
• Don’t fight the implementation paradigm. There’s always another way to think about a domain. Find model concepts that fit the paradigm.
•依靠通用语言。即使工具之间没有严格的联系,语言的一致性使用也能防止设计的各个部分出现偏差。
• Lean on the ubiquitous language. Even when there is no rigorous connection between tools, very consistent use of language can keep parts of the design from diverging.
•不要过于执着于 UML。有时,对某种工具(例如 UML 图)的执着会导致人们为了迎合易于绘制的图形而扭曲模型。例如,UML 的确提供了一些表示约束的功能,但这并不总是足够。采用其他绘图风格(或许是其他范式的惯例),或者使用简单的英文描述,都比费尽心思地去适应某种专为特定对象视图设计的绘图风格要好得多。
• Don’t get hung up on UML. Sometimes the fixation on a tool, such as UML diagramming, leads people to distort the model to make it fit what can easily be drawn. For example, UML does have some features for representing constraints, but they are not always sufficient. Some other style of drawing (perhaps conventional for the other paradigm), or simple English descriptions, are better than tortuous adaptation of a drawing style intended for a certain view of objects.
•保持怀疑态度。这个工具真的能发挥其应有的作用吗?仅仅因为你有一些规则,并不一定意味着你需要规则引擎带来的额外开销。规则可以表示为对象,虽然可能不够简洁;多种范式会使问题变得极其复杂。
• Be skeptical. Is the tool really pulling its weight? Just because you have some rules, that doesn’t necessarily mean you need the overhead of a rules engine. Rules can be expressed as objects, perhaps a little less neatly; multiple paradigms complicate matters enormously.
在采用混合范式之前,应先穷尽主流范式内的所有选项。即使某些领域概念并非显而易见的对象,它们通常也可以在现有范式内进行建模。第九章将探讨如何使用对象技术对非常规概念进行建模。
Before taking on the burden of mixed paradigms, the options within the dominant paradigm should be exhausted. Even though some domain concepts don’t present themselves as obvious objects, they often can be modeled within the paradigm. Chapter 9 will discuss the modeling of unconventional types of concepts using object technology.
关系型范式是范式混合的一种特殊情况。关系型数据库是最常见的非对象技术,但它与其他组件相比,与对象模型的关系更为密切,因为它充当了构成对象本身的数据的持久存储。第 6 章将讨论如何在关系型数据库中存储对象数据,以及对象生命周期中的许多其他挑战。
The relational paradigm is a special case of paradigm mixing. The most common nonobject technology, the relational database is also more intimately related to the object model than other components, because it acts as the persistent store of the data that makes up the objects themselves. Storing object data in relational databases will be discussed in Chapter 6, along with the many other challenges of the object life cycle.
每个对象都有其生命周期。一个对象诞生,可能会经历各种状态,最终消亡——要么被归档,要么被删除。当然,其中许多是简单的、瞬态的对象,只需调用构造函数即可创建,用于一些计算,然后就被垃圾回收器回收。没有必要把这类对象复杂化。但其他对象则拥有更长的生命周期,并非所有时间都驻留在内存中。它们与其他对象之间存在复杂的相互依赖关系。它们会经历状态变化,而这些变化又受到不变量的约束。管理这些对象会带来诸多挑战,很容易使模型驱动设计(MDD)的尝试失败。
Every object has a life cycle. An object is born, it likely goes through various states, and it eventually dies—being either archived or deleted. Of course, many of these are simple, transient objects, created with an easy call to their constructor, used in some computation, and then abandoned to the garbage collector. There is no need to complicate such objects. But other objects have longer lives, not all of which are spent in active memory. They have complex interdependencies with other objects. They go through changes of state to which invariants apply. Managing these objects presents challenges that can easily derail an attempt at MODEL-DRIVEN DESIGN.
图 6.1. 领域对象的生命周期
Figure 6.1. The life cycle of a domain object
The challenges fall into two categories.
1.在整个生命周期中保持完整性
1. Maintaining integrity throughout the life cycle
2.防止模型被生命周期管理的复杂性所淹没
2. Preventing the model from getting swamped by the complexity of managing the life cycle
本章将通过三种模式来解决这些问题。首先,聚合模式通过明确定义所有权和边界来强化模型本身,避免对象之间混乱交织。这种模式对于在生命周期的各个阶段保持完整性至关重要。
This chapter will address these issues through three patterns. First, AGGREGATES tighten up the model itself by defining clear ownership and boundaries, avoiding a chaotic, tangled web of objects. This pattern is crucial to maintaining integrity in all phases of the life cycle.
接下来,重点转向生命周期的初始阶段,使用工厂(Factory)创建和重构复杂对象和聚合(Agregate),并保持其内部结构的封装。最后,存储库(Repositories)处理生命周期的中期和末期,提供查找和检索持久化对象的方法,同时封装涉及的庞大基础设施。
Next, the focus turns to the beginning of the life cycle, using FACTORIES to create and reconstitute complex objects and AGGREGATES, keeping their internal structure encapsulated. Finally, REPOSITORIES address the middle and end of the life cycle, providing the means of finding and retrieving persistent objects while encapsulating the immense infrastructure involved.
尽管存储库和工厂本身并非来自领域,但它们在领域设计中扮演着重要的角色。这些结构通过提供对模型对象的访问,完善了模型驱动设计。
Although REPOSITORIES and FACTORIES do not themselves come from the domain, they have meaningful roles in the domain design. These constructs complete the MODEL-DRIVEN DESIGN by giving us accessible handles on the model objects.
通过对聚合进行建模,并在设计中添加工厂和存储库,我们能够以系统且有意义的单元方式,在模型对象的整个生命周期中对其进行操作。聚合划定了生命周期每个阶段必须维护不变性的范围。工厂和存储库对聚合进行操作,封装了特定生命周期转换的复杂性。
Modeling AGGREGATES and adding FACTORIES and REPOSITORIES to the design gives us the ability to manipulate the model objects systematically and in meaningful units throughout their life cycle. AGGREGATES mark off the scope within which invariants have to be maintained at every stage of the life cycle. FACTORIES and REPOSITORIES operate on AGGREGATES, encapsulating the complexity of specific life cycle transitions.
极简的关联设计有助于简化遍历并在一定程度上限制关系的爆炸式增长,但大多数业务领域都相互关联,以至于我们最终仍然需要追踪冗长而复杂的对象引用路径。从某种程度上说,这种错综复杂的关系反映了现实世界的现实,现实世界很少会给我们清晰的边界。这在软件设计中是一个问题。
Minimalist design of associations helps simplify traversal and limit the explosion of relationships somewhat, but most business domains are so interconnected that we still end up tracing long, deep paths through object references. In a way, this tangle reflects the realities of the world, which seldom obliges us with sharp boundaries. It is a problem in a software design.
假设你要从数据库中删除一个 Person 对象。删除该 Person 对象时,会同时删除姓名、出生日期和职位描述。但是地址呢?同一个地址可能还有其他人。如果你删除了地址,这些 Person 对象就会引用已删除的对象。如果你保留地址,数据库中就会积累大量无用的地址。自动垃圾回收机制可以清除这些无用的地址,但即使你的数据库系统支持这种技术手段,它也忽略了一个基本的建模问题。
Say you were deleting a Person object from a database. Along with the person go a name, birth date, and job description. But what about the address? There could be other people at the same address. If you delete the address, those Person objects will have references to a deleted object. If you leave it, you accumulate junk addresses in the database. Automatic garbage collection could eliminate the junk addresses, but that technical fix, even if available in your database system, ignores a basic modeling issue.
即使考虑单个事务,典型对象模型中错综复杂的关系网络也无法明确界定变更可能造成的影响。仅仅因为存在依赖关系就刷新系统中的每个对象是不切实际的。
Even when considering an isolated transaction, the web of relationships in a typical object model gives no clear limit to the potential effect of a change. It is not practical to refresh every object in the system, just in case there is some dependency.
在多个客户端同时访问同一对象的系统中,这个问题尤为突出。由于许多用户会查询和更新系统中的不同对象,我们必须防止对相互依赖的对象同时进行更改。如果作用域定义错误,将会造成严重后果。
The problem is acute in a system with concurrent access to the same objects by multiple clients. With many users consulting and updating different objects in the system, we have to prevent simultaneous changes to interdependent objects. Getting the scope wrong has serious consequences.
在具有复杂关联的模型中,很难保证对象变更的一致性。需要维护的不变性不仅适用于离散对象,还适用于密切相关的对象组。然而,过于谨慎的锁定机制会导致多个用户之间不必要的相互干扰,从而使系统无法使用。
It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.
换句话说,我们如何知道一个由其他对象组成的对象在哪里开始,在哪里结束?在任何持久化数据存储的系统中,都必须存在一个允许事务更改数据的空间,以及一种维护数据一致性(即维护其不变性)的方法。数据库允许各种锁定方案,并且可以编写测试程序。但是,这些临时解决方案会分散人们对模型本身的注意力,很快你又会回到靠碰运气解决问题的状态。
Put another way, how do we know where an object made up of other objects begins and ends? In any system with persistent storage of data, there must be a scope for a transaction that changes data, and a way of maintaining the consistency of the data (that is, maintaining its invariants). Databases allow various locking schemes, and tests can be programmed. But these ad hoc solutions divert attention away from the model, and soon you are back to hacking and hoping.
事实上,要找到解决这类问题的平衡方案,需要对领域有更深入的理解,这次需要考虑诸如某些类实例之间变化频率等因素。我们需要找到一种模型,它既能放宽高争议点的限制,又能加强严格的不变式约束。
In fact, finding a balanced solution to these kinds of problems calls for deeper understanding of the domain, this time extending to factors such as the frequency of change between the instances of certain classes. We need to find a model that leaves high-contention points looser and strict invariants tighter.
尽管这个问题表面上表现为数据库事务的技术难题,但其根源在于模型本身——缺乏明确的边界。基于模型的解决方案将使模型更易于理解,设计方案也更易于沟通。随着模型的修订,它将指导我们对实现方案的修改。
Although this problem surfaces as technical difficulties in database transactions, it is rooted in the model—in its lack of defined boundaries. A solution driven from the model will make the model easier to understand and make the design easier to communicate. As the model is revised, it will guide our changes to the implementation.
该模型中已开发出用于定义所有权关系的方案。以下这个简洁而严谨的系统,正是从这些概念中提炼而来,它包含了一组用于实现修改对象及其所有者的交易的规则。
Schemes have been developed for defining ownership relationships in the model. The following simple but rigorous system, distilled from those concepts, includes a set of rules for implementing transactions that modify the objects and their owners.1
首先,我们需要一个抽象层来封装模型中的引用。聚合(AGGREGATE)是一组关联对象的集合,为了便于数据变更,我们将其视为一个整体。每个聚合都有一个根节点和一个边界。边界定义了聚合内部包含的内容。根节点是包含在聚合中的一个特定实体。聚合体中只有根节点允许外部对象持有对其的引用,但边界内的对象可以相互持有引用。除根节点之外的实体具有局部标识,但该标识只能在聚合体内部区分,因为任何外部对象都无法在根实体的上下文之外看到它。
First we need an abstraction for encapsulating references within the model. An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE. The root is a single, specific ENTITY contained in the AGGREGATE. The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, although objects within the boundary may hold references to each other. ENTITIES other than the root have local identity, but that identity needs to be distinguishable only within the AGGREGATE, because no outside object can ever see it out of the context of the root ENTITY.
汽车模型可能会被用于汽车修理店的软件中。汽车是一个具有全局标识的实体:我们希望将这辆车与世界上所有其他汽车区分开来,即使是非常相似的汽车。我们可以使用车辆识别码(VIN)来实现这一点,VIN是分配给每辆新车的唯一标识符。我们可能想要追踪轮胎在四个车轮位置上的旋转历史。我们可能想要了解每个轮胎的里程数和胎面磨损情况。为了知道哪个轮胎对应哪个车轮,轮胎本身也必须是可识别的实体。但是,我们不太可能关心这些轮胎在特定车辆之外的身份。如果我们更换轮胎并将旧轮胎送到回收厂,我们的软件要么将不再追踪它们,要么它们将沦为一堆轮胎中的匿名成员。没有人会关心它们的旋转历史。更重要的是,即使轮胎还安装在汽车上,也没有人会尝试查询系统来查找特定的轮胎,然后查看它安装在哪辆车上。他们会查询数据库以找到一辆车,然后请求获取轮胎的临时引用。因此,这辆车是聚合的根实体,其边界也包含了轮胎。另一方面,发动机缸体上刻有序列号,有时会独立于车辆进行跟踪。在某些应用中,发动机可能是其自身聚合的根实体。
A model of a car might be used in software for an auto repair shop. The car is an ENTITY with global identity: we want to distinguish that car from all other cars in the world, even very similar ones. We can use the vehicle identification number for this, a unique identifier assigned to each new car. We might want to track the rotation history of the tires through the four wheel positions. We might want to know the mileage and tread wear of each tire. To know which tire is which, the tires must be identified ENTITIES also. But it is very unlikely that we care about the identity of those tires outside of the context of that particular car. If we replace the tires and send the old ones to a recycling plant, either our software will no longer track them at all, or they will become anonymous members of a heap of tires. No one will care about their rotation histories. More to the point, even while they are attached to the car, no one will try to query the system to find a particular tire and then see which car it is on. They will query the database to find a car and then ask it for a transient reference to the tires. Therefore, the car is the root ENTITY of the AGGREGATE whose boundary encloses the tires also. On the other hand, engine blocks have serial numbers engraved on them and are sometimes tracked independently of the car. In some applications, the engine might be the root of its own AGGREGATE.
图 6.2. 局部与全局标识和对象引用
Figure 6.2. Local versus global identity and object references
不变性(即数据变更时必须维护的一致性规则)涉及聚合(AGGREGATE)成员之间的关系。任何跨越多个聚合的规则都不需要始终保持最新。通过事件处理、批量处理或其他更新机制,其他依赖关系可以在指定时间内得到解决。但是,聚合内部应用的不变性将在每次事务完成后强制执行。
Invariants, which are consistency rules that must be maintained whenever data changes, will involve relationships between members of the AGGREGATE. Any rule that spans AGGREGATES will not be expected to be up-to-date at all times. Through event processing, batch processing, or other update mechanisms, other dependencies can be resolved within some specified time. But the invariants applied within an AGGREGATE will be enforced with the completion of each transaction.
图 6.3.聚合不变量
Figure 6.3. AGGREGATE invariants
现在,为了将这个概念性的聚合转化为实际应用,我们需要一组适用于所有交易的规则。
Now, to translate that conceptual AGGREGATE into the implementation, we need a set of rules to apply to all transactions.
• 根实体具有全局标识,并最终负责检查不变性。
• The root ENTITY has global identity and is ultimately responsible for checking invariants.
• 根实体具有全局标识。边界内的实体具有局部标识,仅在聚合内唯一。
• Root ENTITIES have global identity. ENTITIES inside the boundary have local identity, unique only within the AGGREGATE.
•除了根实体之外,聚合边界之外的任何对象都不能持有对内部任何对象的引用。根实体可以将对内部实体的引用传递给其他对象。但这些对象只能临时使用它们,并且它们可能不会保留引用。根对象可以将一个值对象的副本传递给另一个对象,而该副本的后续处理无关紧要,因为它只是一个值,不再与聚合有任何关联。
• Nothing outside the AGGREGATE boundary can hold a reference to anything inside, except to the root ENTITY. The root ENTITY can hand references to the internal ENTITIES to other objects, but those objects can use them only transiently, and they may not hold on to the reference. The root may hand a copy of a VALUE OBJECT to another object, and it doesn’t matter what happens to it, because it’s just a VALUE and no longer will have any association with the AGGREGATE.
• 作为前述规则的推论,只有聚合根可以直接通过数据库查询获得。所有其他对象都必须通过遍历关联关系来查找。
• As a corollary to the previous rule, only AGGREGATE roots can be obtained directly with database queries. All other objects must be found by traversal of associations.
• AGGREGATE中的对象可以持有对其他AGGREGATE根的引用。
• Objects within the AGGREGATE can hold references to other AGGREGATE roots.
• 删除操作必须一次性移除聚合边界内的所有内容。(有了垃圾回收机制,这很容易做到。因为除了根节点之外,没有任何其他节点的外部引用,所以删除根节点,其他所有节点都会被回收。)
• A delete operation must remove everything within the AGGREGATE boundary at once. (With garbage collection, this is easy. Because there are no outside references to anything but the root, delete the root and everything else will be collected.)
• 当对AGGREGATE边界内的任何对象进行更改时,必须满足整个AGGREGATE的所有不变量。
• When a change to any object within the AGGREGATE boundary is committed, all invariants of the whole AGGREGATE must be satisfied.
将实体和值对象聚类成聚合,并为每个聚合定义边界。选择一个实体作为每个聚合的根,并通过该根控制对边界内对象的所有访问。仅允许外部对象持有对根的引用。对内部成员的临时引用只能在单个操作中使用。由于根控制着访问,因此它不会受到内部结构更改的影响。这种安排使得在任何状态更改中,都能有效地强制执行聚合中对象以及整个聚合的所有不变式。
Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as a whole in any state change.
如果有一个技术框架,允许你声明聚合(AGREGATES) ,并自动执行锁定机制等操作,那将非常有用。如果没有这种帮助,团队必须自律地就聚合达成一致,并始终按照这些聚合编写代码。
It can be very helpful to have a technical framework that allows you to declare AGGREGATES and then automatically carries out the locking scheme and so forth. Without that assistance, the team must have the self-discipline to agree on the AGGREGATES and code consistently with them.
考虑一下简化采购订单系统可能带来的复杂情况。
Consider the complications possible in a simplified purchase order system.
图 6.4. 采购订单系统模型
Figure 6.4. A model for a purchase order system
此图展示了采购订单 (PO) 的一种相当传统的视图,订单被分解为多个明细项,并遵循一个不变的规则:所有明细项的总和不能超过整个采购订单的限额。现有的实现方式存在三个相互关联的问题。
This diagram presents a pretty conventional view of a purchase order (PO), broken down into line items, with an invariant rule that the sum of the line items can’t exceed the limit for the PO as a whole. The existing implementation has three interrelated problems.
1. 不变式强制执行。当添加新的行项目时,采购订单会检查总计,如果某个项目使总计超过限制,则采购订单本身会被标记为无效。正如我们将看到的,这并非充分的保护措施。
1. Invariant enforcement. When a new line item is added, the PO checks the total and marks itself invalid if an item pushes it over the limit. As we’ll see, this is not adequate protection.
2. 变更管理。当采购订单被删除或归档时,其行项目也会随之删除,但该模型并未提供关于何时停止跟踪这些关系的指导。此外,对于在不同时间更改零件价格的影响也存在困惑。
2. Change management. When the PO is deleted or archived, the line items are taken along, but the model gives no guidance on where to stop following the relationships. There is also confusion about the impact of changing the part price at different times.
3. 数据库共享。多个用户在数据库中造成了争用问题。
3. Sharing the database. Multiple users are creating contention problems in the database.
多个用户将同时输入和更新不同的采购订单,我们需要防止他们互相干扰工作。我们先从一个非常简单的策略开始:锁定用户开始编辑的任何对象,直到该用户提交事务。这样,当 George 编辑第 001 行项目时,Amanda 就无法访问它。但她可以编辑任何其他采购订单中的任何其他行项目(包括 George 正在编辑的采购订单中的其他项目)。
Multiple users will be entering and updating various POs concurrently, and we have to prevent them from messing up each other’s work. Let’s start with a very simple strategy, in which we lock any object a user begins to edit until that user commits the transaction. So, when George is editing line item 001, Amanda cannot access it. She can edit any other line item on any other PO (including other items in the PO George is working on).
图 6.5. 数据库中存储的 PO 的初始条件
Figure 6.5. The initial condition of the PO stored in the database
对象将从数据库读取并实例化到每个用户的内存空间中。用户可以在内存空间中查看和编辑这些对象。只有在开始编辑时才会请求数据库锁。因此,只要乔治和阿曼达不接触彼此的项目,他们就可以同时工作。一切都很顺利……直到乔治和阿曼达开始处理同一个采购订单中的不同行项目。
Objects will be read from the database and instantiated in each user’s memory space. There they can be viewed and edited. Database locks will be requested only when an edit begins. So both George and Amanda can work concurrently, as long as they stay away from each other’s items. All is well . . . until both George and Amanda start working on separate line items in the same PO.
图 6.6. 不同事务中的同时编辑
Figure 6.6. Simultaneous edits in distinct transactions
对用户和他们的软件来说,一切看起来都很正常,因为他们忽略了事务处理期间数据库其他部分发生的更改,而且两个锁定的行项目都没有涉及另一个用户的更改。
Everything looks fine to both users and to their software because they ignore changes to other parts of the database that happen during the transaction, and neither locked line item is involved in the other user’s change.
图 6.7. 生成的 PO 违反了批准限制(打破了不变性)。
Figure 6.7. The resulting PO violates the approval limit (broken invariant).
两位用户都保存了更改后,数据库中存储的采购订单违反了领域模型的不变性。一条重要的业务规则被打破了。然而,竟然无人知晓。
After both users have saved their changes, a PO is stored in the database that violates the invariant of the domain model. An important business rule has been broken. And nobody even knows.
显然,锁定单个订单项不足以起到安全保障作用。如果我们一次性锁定整个采购订单,就能避免这个问题。
Clearly, locking a single line item isn’t an adequate safeguard. If instead we had locked an entire PO at a time, the problem would have been prevented.
图 6.8. 锁定整个 PO 可以强制执行不变量。
Figure 6.8. Locking the entire PO allows the invariant to be enforced.
在阿曼达解决问题之前(例如提高限额或删除一把吉他),程序不会允许保存此交易。这种机制可以防止问题发生,如果工作主要分散在多个采购订单中,这可能是一个不错的解决方案。但如果多人通常同时处理大型采购订单的不同项目,那么这种锁定机制就会变得很麻烦。
The program will not allow this transaction to be saved until Amanda has resolved the problem, perhaps by raising the limit or by eliminating a guitar. This mechanism prevents the problem, and it may be a fine solution if work is mostly spread widely across many POs. But if multiple people typically work simultaneously on different line items of a large PO, then this locking will get cumbersome.
即使假设存在许多小订单,仍然有其他方法可以违反该断言。考虑一下“部分”这个问题。如果有人在阿曼达添加订单时更改了长号的价格,这难道不也违反了该不变式吗?
Even assuming many small POs, there are other ways to violate the assertion. Consider that “part.” If someone changed the price of a trombone while Amanda was adding to her order, wouldn’t that violate the invariant too?
我们开始吧尝试锁定该零件以及整个采购订单。以下是当 George、Amanda 和 Sam 分别处理不同的采购订单时发生的情况:
Let’s try locking the part in addition to the entire PO. Here’s what happens when George, Amanda, and Sam are working on different POs:
图 6.9. 过度谨慎的锁定正在干扰人们的工作。
Figure 6.9. Over-cautious locking is interfering with people’s work.
不便之处越来越多,因为大家对这些仪器(“零件”)的争夺非常激烈。然后:
The inconvenience is mounting, because there is a lot of contention for the instruments (the “parts”). And then:
图 6.10. 死锁
Figure 6.10. Deadlock
那三个人还得等一阵子。
Those three will be waiting a while.
At this point we can begin to improve the model by incorporating the following knowledge of the business:
1.零件在许多采购订单中使用(高竞争性)。
1. Parts are used in many POs (high contention).
2.零件的变更次数比采购订单的变更次数少。
2. There are fewer changes to parts than there are to POs.
3.零件价格的变动不一定会反映在现有的采购订单中。这取决于价格变动发生的时间与采购订单状态之间的关系。
3. Changes to part prices do not necessarily propagate to existing POs. It depends on the time of a price change relative to the status of the PO.
第三点在考虑已交付的存档采购订单时尤为明显。这些订单当然应该显示当时的订单价格,而不是当前价格。
Point 3 is particularly obvious when we consider archived POs that have already been delivered. They should, of course, show the prices as of the time they were filled, rather than current prices.
图 6.11. 价格被复制到行项目中。现在可以强制执行聚合不变量。
Figure 6.11. Price is copied into Line Item. AGGREGATE invariant can now be enforced.
符合此模型的实现方式能够保证采购订单 (PO) 与其物料之间的关联不变性,同时,零件价格的变动无需立即影响引用该零件的物料。更广泛的一致性规则可以通过其他方式实现。例如,系统可以每天向用户展示一个价格过时的物料队列,以便用户更新或排除每个物料。但这并非必须始终强制执行的不变性。通过放宽物料对零件的依赖性,我们可以避免冲突,并更好地反映业务实际情况。同时,加强采购订单与其物料之间的关联性能够确保一项重要的业务规则得到遵循。
An implementation consistent with this model would guarantee the invariant relating PO and its items, while changes to the price of a part would not have to immediately affect the items that reference it. Broader consistency rules could be addressed in other ways. For example, the system could present a queue of items with outdated prices to the users each day, so they could update or exempt each one. But this is not an invariant that must be enforced at all times. By making the dependency of line items on parts looser, we avoid contention and reflect the realities of the business better. At the same time, tightening the relationship of the PO and its line items guarantees that an important business rule will be followed.
AGGREGATE规定了采购订单及其物料的所有权,这与商业惯例相符。创建和删除采购订单和物料自然是关联在一起的,而零件的创建和删除是独立的。
The AGGREGATE imposes an ownership of the PO and its items that is consistent with business practice. The creation and deletion of a PO and items are naturally tied together, while the creation and deletion of parts is independent.
聚合(AGGREGATES)划定了生命周期中每个阶段都必须维护不变性的范围。以下模式,即工厂(FACTORIES)和存储库(REPOSITORIES),均基于聚合进行操作,封装了特定生命周期转换的复杂性。
AGGREGATES mark off the scope within which invariants have to be maintained at every stage of the life cycle. The following patterns, FACTORIES and REPOSITORIES, operate on AGGREGATES, encapsulating the complexity of specific life cycle transitions. . . .
当创建对象或整个聚合时,如果操作变得复杂或暴露了太多内部结构,工厂就会提供封装。
When creation of an object, or an entire AGGREGATE, becomes complicated or reveals too much of the internal structure, FACTORIES provide encapsulation.
物品的力量很大程度上源于其内部结构的精妙构造及其相互关联。物品应当精简至只剩下与其意义或互动功能无关的部分。这种生命周期中期的责任已足够。问题在于,如果让一个复杂的物品承担其自身创造的责任,就会产生过重的负担。
Much of the power of objects rests in the intricate configuration of their internals and their associations. An object should be distilled until nothing remains that does not relate to its meaning or support its role in interactions. This mid-life cycle responsibility is plenty. Problems arise from overloading a complex object with responsibility for its own creation.
汽车发动机是一台精密复杂的机械,由数十个零件协同工作,完成其驱动轴旋转的任务。我们可以想象,设计一个发动机缸体,能够抓住一组活塞并将其装入气缸,火花塞能够找到插座并自行拧紧。但如此复杂的机器不太可能像我们常见的发动机那样可靠高效。因此,我们接受由其他东西来组装这些零件。也许是人类机械师,也许是……将会是一个工业机器人。机器人和人实际上都比他们组装的发动机更复杂。组装零件的工作与旋转轴的工作完全无关。装配人员只在汽车制造过程中工作——开车时你不需要机器人或机械师。因为汽车的组装和行驶永远不会同时进行,所以将这两种功能合并到同一个机构中毫无意义。同样,组装一个复杂的复合物体最好与该物体完成后需要执行的任何任务分开。
A car engine is an intricate piece of machinery, with dozens of parts collaborating to perform the engine’s responsibility: to turn a shaft. One could imagine trying to design an engine block that could grab on to a set of pistons and insert them into its cylinders, spark plugs that would find their sockets and screw themselves in. But it seems unlikely that such a complicated machine would be as reliable or as efficient as our typical engines are. Instead, we accept that something else will assemble the pieces. Perhaps it will be a human mechanic or perhaps it will be an industrial robot. Both the robot and the human are actually more complex than the engine they assemble. The job of assembling parts is completely unrelated to the job of spinning a shaft. The assemblers function only during the creation of the car—you don’t need a robot or a mechanic with you when you’re driving. Because cars are never assembled and driven at the same time, there is no value in combining both of these functions into the same mechanism. Likewise, assembling a complex compound object is a job that is best separated from whatever job that object will have to do when it is finished.
但将责任转移给应用程序中的另一个相关方——客户端对象——会导致更严重的问题。客户端知道需要完成什么任务,并依赖领域对象来执行必要的计算。如果客户端需要组装所需的领域对象,它就必须了解对象的内部结构。为了强制执行适用于领域对象各部分关系的所有不变式,客户端必须了解对象的一些规则。即使是调用构造函数也会将客户端与其正在构建的对象的具体类耦合在一起。如果不修改客户端,就无法对领域对象的实现进行任何更改,这使得重构更加困难。
But shifting responsibility to the other interested party, the client object in the application, leads to even worse problems. The client knows what job needs to be done and relies on the domain objects to carry out the necessary computations. If the client is expected to assemble the domain objects it needs, it must know something about the internal structure of the object. In order to enforce all the invariants that apply to the relationship of parts in the domain object, the client must know some of the object’s rules. Even calling constructors couples the client to the concrete classes of the objects it is building. No change to the implementation of the domain objects can be made without changing the client, making refactoring harder.
客户端承担对象创建任务会变得不必要地复杂,并模糊其职责。这破坏了领域对象和所创建聚合的封装性。更糟糕的是,如果客户端是应用层的一部分,那么职责就完全泄露出了领域层。这种应用程序与具体实现的紧密耦合,剥夺了领域层抽象的大部分优势,并使持续的变更成本越来越高。
A client taking on object creation becomes unnecessarily complicated and blurs its responsibility. It breaches the encapsulation of the domain objects and the AGGREGATES being created. Even worse, if the client is part of the application layer, then responsibilities have leaked out of the domain layer altogether. This tight coupling of the application to the specifics of the implementation strips away most of the benefits of abstraction in the domain layer and makes continuing changes ever more expensive.
创建对象本身可能是一项重大操作,但复杂的组装操作并不适合被创建对象的职责。将这些职责混杂在一起会导致设计笨拙且难以理解。让客户端直接进行构造会使客户端设计变得混乱,破坏组装对象或聚合的封装性,并使客户端与被创建对象的实现过度耦合。
Creation of an object can be a major operation in itself, but complex assembly operations do not fit the responsibility of the created objects. Combining such responsibilities can produce ungainly designs that are hard to understand. Making the client direct construction muddies the design of the client, breaches encapsulation of the assembled object or AGGREGATE, and overly couples the client to the implementation of the created object.
复杂对象的创建是领域层的职责,但这项任务并不属于表达模型的对象本身。在某些情况下,对象的创建和组装对应于……领域内的重要里程碑事件,例如“开设银行账户”。但对象的创建和组装通常在领域内没有实际意义;它们只是实现过程中的必要步骤。为了解决这个问题,我们必须在领域设计中添加一些既非实体、值对象也非服务的构造。这与上一章有所不同,因此必须明确这一点:我们向设计中添加的元素与模型中的任何内容都不对应,但它们仍然是领域层职责的一部分。
Complex object creation is a responsibility of the domain layer, yet that task does not belong to the objects that express the model. There are some cases in which an object creation and assembly corresponds to a milestone significant in the domain, such as “open a bank account.” But object creation and assembly usually have no meaning in the domain; they are a necessity of the implementation. To solve this problem, we have to add constructs to the domain design that are not ENTITIES, VALUE OBJECTS, or SERVICES. This is a departure from the previous chapter, and it is important to make the point clear: We are adding elements to the design that do not correspond to anything in the model, but they are nonetheless part of the domain layer’s responsibility.
每种面向对象语言都提供了创建对象的机制(例如 Java 和 C++ 中的构造函数,以及 Smalltalk 中的实例创建类方法),但我们需要更抽象的、与其他对象解耦的构造机制。负责创建其他对象的程序元素称为工厂(FACTORY)。
Every object-oriented language provides a mechanism for creating objects (constructors in Java and C++, instance creation class methods in Smalltalk, for example), but there is a need for more abstract construction mechanisms that are decoupled from the other objects. A program element whose responsibility is the creation of other objects is called a FACTORY.
图 6.12. 与工厂的基本交互
Figure 6.12. Basic interactions with a FACTORY
正如对象的接口应该封装其实现,从而使客户端能够在不了解其工作原理的情况下使用对象的行为一样,工厂(FACTORY)封装了创建复杂对象或聚合(AGGREGATE)所需的知识。它提供了一个反映客户端目标的接口,以及对所创建对象的抽象视图。
Just as the interface of an object should encapsulate its implementation, thus allowing a client to use the object’s behavior without knowing how it works, a FACTORY encapsulates the knowledge needed to create a complex object or AGGREGATE. It provides an interface that reflects the goals of the client and an abstract view of the created object.
所以:
Therefore:
将创建复杂对象和聚合实例的职责转移到一个单独的对象,该对象本身可能在领域模型中没有职责,但仍然是领域设计的一部分。提供一个封装所有复杂程序集的接口,该接口不需要客户端引用被实例化对象的具体类。将整个聚合作为一个整体创建,并强制执行其不变式。
Shift the responsibility for creating instances of complex objects and AGGREGATES to a separate object, which may itself have no responsibility in the domain model but is still part of the domain design. Provide an interface that encapsulates all complex assembly and that does not require the client to reference the concrete classes of the objects being instantiated. Create entire AGGREGATES as a piece, enforcing their invariants.
设计工厂(FACTORIES)的方法有很多种。Gamma等人在 1995 年的著作中详细阐述了几种专用创建模式,例如工厂方法(FACTORY METHOD)、抽象工厂(ABSTRACT FACTORY)和建造者(BUILDER)。该书主要探讨了最复杂的对象构造问题的模式。本文的重点并非深入探讨工厂的设计,而是展示工厂作为领域设计重要组成部分的地位。合理使用工厂有助于确保模型驱动设计(MODEL-DRIVEN DESIGN)的正确性。
There are many ways to design FACTORIES. Several special-purpose creation patterns—FACTORY METHOD, ABSTRACT FACTORY, and BUILDER—were thoroughly treated in Gamma et al. 1995. That book mostly explored patterns for the most difficult object construction problems. The point here is not to delve deeply into designing FACTORIES, but rather to show the place of FACTORIES as important components of a domain design. Proper use of FACTORIES can help keep a MODEL-DRIVEN DESIGN on track.
任何一家好的工厂都必须具备两个基本要求:
The two basic requirements for any good FACTORY are
1.每个创建方法都是原子性的,并且强制执行所创建对象或聚合的所有不变式。工厂应该只能生成处于一致状态的对象。对于实体而言,这意味着创建整个聚合,满足所有不变式,但可能仍有一些可选元素需要添加。对于不可变值对象而言,这意味着所有属性都初始化为其正确的最终状态。如果接口允许请求一个无法正确创建的对象,则应该抛出异常或调用其他机制,以确保不会返回任何不正确的值。
1. Each creation method is atomic and enforces all invariants of the created object or AGGREGATE. A FACTORY should only be able to produce an object in a consistent state. For an ENTITY, this means the creation of the entire AGGREGATE, with all invariants satisfied, but probably with optional elements still to be added. For an immutable VALUE OBJECT, this means that all attributes are initialized to their correct final state. If the interface makes it possible to request an object that can’t be created correctly, then an exception should be raised or some other mechanism should be invoked that will ensure that no improper return value is possible.
2.工厂模式应该抽象成所需的类型,而不是具体创建的类。Gamma等人在 1995 年提出的复杂工厂模式对此有所帮助。
2. The FACTORY should be abstracted to the type desired, rather than the concrete class(es) created. The sophisticated FACTORY patterns in Gamma et al. 1995 help with this.
一般来说,你会创建一个工厂来制造你想要隐藏其细节的东西,并将这个工厂放置在你想要控制的位置。这些决策通常围绕着聚合(AGGREGATES)展开。
Generally speaking, you create a factory to build something whose details you want to hide, and you place the FACTORY where you want the control to be. These decisions usually revolve around AGGREGATES.
例如,如果您需要在已存在的聚合 (AGGREGATE)中添加元素,则可以在聚合的根节点上创建一个工厂方法 (FACTORY METHOD) 。这样,聚合内部的实现就对任何外部客户端隐藏起来,同时又赋予根节点确保聚合在添加元素时保持完整性的责任,如图6.13所示(见下一页)。
For example, if you needed to add elements inside a preexisting AGGREGATE, you might create a FACTORY METHOD on the root of the AGGREGATE. This hides the implementation of the interior of the AGGREGATE from any external client, while giving the root responsibility for ensuring the integrity of the AGGREGATE as elements are added, as shown in Figure 6.13 on the next page.
图 6.13.工厂方法封装了聚合的扩展。
Figure 6.13. A FACTORY METHOD encapsulates expansion of an AGGREGATE.
另一个例子是,将工厂方法放在一个与生成另一个对象密切相关的对象上,尽管该对象在生成后并不拥有该产品。当一个对象的数据(可能还有规则)在创建另一个对象的过程中起着至关重要的作用时,就需要这样做。这样就避免了从生成器中提取信息以供其他地方创建对象。它还传达了生成器和产品之间的特殊关系。
Another example would be to place a FACTORY METHOD on an object that is closely involved in spawning another object, although it doesn’t own the product once it is created. When the data and possibly the rules of one object are very dominant in the creation of an object, this saves pulling information out of the spawner to be used elsewhere to create the object. It also communicates the special relationship between the spawner and the product.
在图 6.14中,交易订单与经纪账户不属于同一个聚合(AGGREGATE),原因很简单:首先,交易订单需要与交易执行应用程序交互,而经纪账户的存在会阻碍交易执行。即便如此,赋予经纪账户创建交易订单的控制权似乎也很自然。经纪账户包含将嵌入交易订单的信息(首先是其自身的标识),并且包含管理允许交易的规则。此外,隐藏交易订单的具体实现也可能带来好处。例如,可以将其重构为一个层次结构,为买入订单和卖出订单分别创建独立的子类。工厂(FACTORY)机制可以避免客户端与具体类耦合。
In Figure 6.14, the Trade Order is not part of the same AGGREGATE as the Brokerage Account because, for a start, it will go on to interact with the trade execution application, where the Brokerage Account would only be in the way. Even so, it seems natural to give the Brokerage Account control over the creation of Trade Orders. The Brokerage Account contains information that will be embedded in the Trade Order (starting with its own identity), and it contains rules that govern what trades are allowed. We might also benefit from hiding the implementation of Trade Order. For example, it might be refactored into a hierarchy, with separate subclasses for Buy Order and Sell Order. The FACTORY keeps the client from being coupled to the concrete classes.
图 6.14.工厂方法生成一个不属于同一聚合的实体。
Figure 6.14. A FACTORY METHOD spawns an ENTITY that is not part of the same AGGREGATE.
工厂(FACTORY)与其产品紧密耦合,因此工厂应该只附加到与产品具有密切自然关系的对象上。当我们想要隐藏某些内容(无论是具体的实现细节还是复杂的构造过程),但又找不到合适的载体时,就必须创建一个专用的工厂对象或服务(SERVICE)。一个独立的工厂通常会生成一个完整的聚合(AGGREGATE),并提供指向根的引用,同时确保产品聚合的不变式得到强制执行。如果聚合内部的某个对象需要一个工厂,而聚合根又不是合适的存放位置,那么可以创建一个独立的工厂。但务必遵守聚合内部的访问限制规则,并确保聚合外部对产品的引用都是临时性的。
A FACTORY is very tightly coupled to its product, so a FACTORY should be attached only to an object that has a close natural relationship with the product. When there is something we want to hide—either the concrete implementation or the sheer complexity of construction—yet there doesn’t seem to be a natural host, we must create a dedicated FACTORY object or SERVICE. A standalone FACTORY usually produces an entire AGGREGATE, handing out a reference to the root, and ensuring that the product AGGREGATE’S invariants are enforced. If an object interior to an AGGREGATE needs a FACTORY, and the AGGREGATE root is not a reasonable home for it, then go ahead and make a standalone FACTORY. But respect the rules limiting access within an AGGREGATE, and make sure there are only transient references to the product from outside the AGGREGATE.
图 6.15. 一个独立的FACTORY构建AGREGREGATE。
Figure 6.15. A standalone FACTORY builds AGGREGATE.
我见过太多代码,其中所有实例都是通过直接调用类构造函数(或者编程语言中其他最基本的实例创建方式)创建的。引入工厂(Factory)机制有很多优点,但通常没有得到充分利用。然而,在某些情况下,构造函数的直接性反而使其成为最佳选择。工厂机制实际上可能会使不使用多态性的简单对象变得晦涩难懂。
I’ve seen far too much code in which all instances are created by directly calling class constructors, or whatever the primitive level of instance creation is for the programming language. The introduction of FACTORIES has great advantages, and is generally underused. Yet there are times when the directness of a constructor makes it the best choice. FACTORIES can actually obscure simple objects that don’t use polymorphism.
在以下情况下,权衡利弊后,使用裸露的公共构造函数更为有利。
The trade-offs favor a bare, public constructor in the following circumstances.
• 该类就是类型。它不属于任何有趣的层次结构,也不会通过实现接口而以多态方式使用。
• The class is the type. It is not part of any interesting hierarchy, and it isn’t used polymorphically by implementing an interface.
• 客户关心实施情况,或许以此作为选择策略的一种方式。
• The client cares about the implementation, perhaps as a way of choosing a STRATEGY.
•对象的所有属性都可供客户端使用,因此不会在向客户端公开的构造函数中嵌套对象创建操作。
• All of the attributes of the object are available to the client, so that no object creation gets nested inside the constructor exposed to the client.
• 施工并不复杂。
• The construction is not complicated.
• 公共构造函数必须遵循与工厂相同的规则:它必须是一个原子操作,满足所创建对象的所有不变性。
• A public constructor must follow the same rules as a FACTORY: It must be an atomic operation that satisfies all invariants of the created object.
避免在其他类的构造函数中调用构造函数。构造函数应该极其简单。复杂的程序集,尤其是聚合类,需要使用工厂方法。使用工厂方法的门槛并不高。
Avoid calling constructors within constructors of other classes. Constructors should be dead simple. Complex assemblies, especially of AGGREGATES, call for FACTORIES. The threshold for choosing to use a little FACTORY METHOD isn’t high.
Java 类库提供了一些有趣的例子。所有集合都实现了接口,从而将客户端与具体实现解耦。然而,它们都是通过直接调用构造函数创建的。其实,可以使用工厂(Factory)来封装集合层次结构。工厂的方法可以允许客户端请求所需的功能,并由工厂选择合适的类进行实例化。这样,创建集合的代码会更加简洁,而且添加新的集合类也不会破坏任何 Java 程序。
The Java class library offers interesting examples. All collections implement interfaces that decouple the client from the concrete implementation. Yet they are all created by direct calls to constructors. A FACTORY could have encapsulated the collection hierarchy. The FACTORY’s methods could have allowed a client to ask for the features it needed, with the FACTORY selecting the appropriate class to instantiate. Code that created collections would be more expressive, and new collection classes could be installed without breaking every Java program.
但具体构造函数也有其优势。首先,对于许多应用程序来说,实现方式的选择可能对性能非常敏感,因此应用程序可能需要控制权。(即便如此,一个真正智能的工厂模式也能应对这些因素。)无论如何,集合类并不多,所以选择起来并不复杂。
But there is a case in favor of the concrete constructors. First, the choice of implementation can be performance sensitive for many applications, so an application might want control. (Even so, a really smart FACTORY could accommodate such factors.) Anyway, there aren’t very many collection classes, so it isn’t that complicated to choose.
尽管抽象集合类型缺少工厂(FACTORY),但由于其使用模式,它们仍然保留了一些价值。集合通常在一个地方创建,在另一个地方使用。这意味着最终使用集合的客户端(添加、删除和检索其内容)仍然可以与接口交互,而无需依赖具体的实现。集合类的选择通常由集合的所有者对象或其工厂(FACTORY)决定。
The abstract collection types preserve some value in spite of the lack of a FACTORY because of their usage patterns. Collections are very often created in one place and used in another. This means that the client that ultimately uses the collection—adding, removing, and retrieving its contents—can still talk to the interface and be decoupled from the implementation. The selection of a collection class typically falls to the object that owns the collection, or to the owning object’s FACTORY.
在设计FACTORY的方法签名时,无论是独立方法还是FACTORY 方法,请记住以下两点。
When designing the method signature of a FACTORY, whether standalone or FACTORY METHOD, keep in mind these two points.
•每个操作都必须是原子性的。你必须在与工厂的单次交互中传入创建完整产品所需的一切信息。你还需要决定如果创建失败(例如,某些不变式未满足)会发生什么。你可以抛出异常,也可以直接返回 null。为了保持一致性,建议为工厂中的失败情况制定一套编码规范。
• Each operation must be atomic. You have to pass in everything needed to create a complete product in a single interaction with the FACTORY. You also have to decide what will happen if creation fails, in the event that some invariant isn’t satisfied. You could throw an exception or just return a null. To be consistent, consider adopting a coding standard for failures in FACTORIES.
•工厂函数与其参数之间存在耦合关系。如果您在选择输入参数时不够谨慎,可能会造成复杂的依赖关系。耦合程度取决于您对参数的处理方式。如果只是简单地将参数插入到产品中,则依赖关系较弱。如果您从参数中选取部分用于构造函数,则耦合程度会更高。
• The FACTORY will be coupled to its arguments. If you are not careful in your selection of input parameters, you can create a rat’s nest of dependencies. The degree of coupling will depend on what you do with the argument. If it is simply plugged into the product, you’ve created a modest dependency. If you are picking parts out of the argument to use in the construction, the coupling gets tighter.
最安全的参数来自较低的设计层。即使在同一层内,也往往存在自然分层,其中包含更多基础对象,供更高层对象使用。(这种分层将在第十章“柔性设计”和第十六章“大型结构”中以不同的方式进行讨论。)
The safest parameters are those from a lower design layer. Even within a layer, there tend to be natural strata with more basic objects that are used by higher level objects. (Such layering will be discussed in different ways in Chapter 10, “Supple Design,” and again in Chapter 16, “Large-Scale Structure.”)
另一个不错的参数选择是与模型中的产品密切相关的对象,这样就不会引入新的依赖关系。在前面的采购订单项示例中,工厂方法接受一个目录部件作为参数,这是该项的必要关联。这在采购订单类和该部件之间添加了直接依赖关系。但这三个对象在概念上构成一个紧密的组。采购订单的 聚合对象已经引用了该部件。因此,将控制权交给聚合对象的根节点并封装聚合对象的内部结构是一个不错的折衷方案。
Another good choice of parameter is an object that is closely related to the product in the model, so that no new dependency is being added. In the earlier example of a Purchase Order Item, the FACTORY METHOD takes a Catalog Part as an argument, which is an essential association for the Item. This adds a direct dependency between the Purchase Order class and the Part. But these three objects form a close conceptual group. The Purchase Order’s AGGREGATE already referenced the Part, anyway. So giving control to the AGGREGATE root and encapsulating the AGGREGATE’S internal structure is a good trade-off.
使用参数的抽象类型,而不是它们的具体类。FACTORY 类与产品的具体类耦合,但不需要也与具体参数耦合。
Use the abstract type of the arguments, not their concrete classes. The FACTORY is coupled to the concrete class of the products; it does not need to be coupled to concrete parameters also.
工厂负责确保其创建的对象或聚合满足所有不变式;但是,在移除适用于外部对象的规则之前,务必三思。工厂可以将不变式检查委托给产品,这通常是最佳做法。
A FACTORY is responsible for ensuring that all invariants are met for the object or AGGREGATE it creates; yet you should always think twice before removing the rules applying to an object outside that object. The FACTORY can delegate invariant checking to the product, and this is often best.
但工厂(FACTORY)与其产品之间存在着特殊的关系。它们已经了解产品的内部结构,其存在的全部意义都在于实现该产品。在某些情况下,将不变逻辑放在工厂中,减少产品中的冗余代码,是有益的。这对于聚合规则(跨越多个对象)尤其适用。而对于附加到其他领域对象的工厂方法,则尤其不适用。
But FACTORIES have a special relationship with their products. They already know their product’s internal structure, and their entire reason for being involves the implementation of their product. Under some circumstances, there are advantages to placing invariant logic in the FACTORY and reducing clutter in the product. This is especially appealing with AGGREGATE rules (which span many objects). It is especially unappealing with FACTORY METHODS attached to other domain objects.
原则上,不变量在每次操作结束时都会生效,但通常情况下,对象允许的转换根本不会触发这些不变量的生效。可能存在一条规则,用于赋值实体的标识属性。但实体创建后,其标识属性是不可变的。值对象则完全不可变。对象无需携带在其生命周期内永远不会应用的逻辑。在这种情况下,工厂是放置不变量的理想位置,可以简化产品。
Although in principle invariants apply at the end of every operation, often the transformations allowed to the object can never bring them into play. There might be a rule that applies to the assignment of the identity attributes of an ENTITY. But after creation that identity is immutable. VALUE OBJECTS are completely immutable. An object doesn’t need to carry around logic that will never be applied in its active lifetime. In such cases, the FACTORY is a logical place to put invariants, keeping the product simpler.
实体工厂与值对象工厂有两个区别。值对象是不可变的;产品以其最终形式完整输出。因此,工厂操作必须允许对产品进行完整描述。实体工厂通常只接受构成有效聚合所需的基本属性。如果某些细节并非由不变量要求,则可以稍后添加。
ENTITY FACTORIES differ from VALUE OBJECT FACTORIES in two ways. VALUE OBJECTS are immutable; the product comes out complete in its final form. So the FACTORY operations have to allow for a full description of the product. ENTITY FACTORIES tend to take just the essential attributes required to make a valid AGGREGATE. Details can be added later if they are not required by an invariant.
接下来是为实体(ENTITY)分配标识所涉及的问题——这与值对象(VALUE OBJECT)无关。正如第 5 章所述,标识符可以由程序自动分配,也可以由外部提供,通常由用户提供。如果要通过电话号码跟踪客户身份,那么显然必须将该电话号码作为参数传递给工厂(FACTORY)。当程序分配标识符时,工厂(FACTORY)是控制标识符的好地方。虽然唯一跟踪 ID 的实际生成通常是通过数据库“序列”或其他基础设施机制完成的,但 FACTORY知道需要请求什么以及将其放在哪里。
Then there are the issues involved in assigning identity to an ENTITY—irrelevant to a VALUE OBJECT. As pointed out in Chapter 5, an identifier can either be assigned automatically by the program or supplied from the outside, typically by the user. If a customer’s identity is to be tracked by the telephone number, then that telephone number must obviously be passed in as an argument to the FACTORY. When the program is assigning an identifier, the FACTORY is a good place to control it. Although the actual generation of a unique tracking ID is typically done by a database “sequence” or other infrastructure mechanism, the FACTORY knows what to ask for and where to put it.
到目前为止,工厂在对象生命周期的最初阶段就发挥了作用。大多数对象最终都会被存储在数据库中或通过网络传输,而目前很少有数据库技术能够保留其内容的原始特性。大多数传输方法会将对象扁平化,使其呈现更加有限的形式。因此,检索对象需要一个可能非常复杂的过程,即重新组装各个部分,使其恢复为一个完整的对象。
Up to this point, the FACTORY has played its part in the very beginning of an object’s life cycle. At some point, most objects get stored in databases or transmitted through a network, and few current database technologies retain the object character of their contents. Most transmission methods flatten an object into an even more limited presentation. Therefore, retrieval requires a potentially complex process of reassembling the parts into a live object.
用于重组的工厂与用于创造的工厂非常相似,但有两个主要区别。
A FACTORY used for reconstitution is very similar to one used for creation, with two major differences.
1.用于重构的 实体工厂不会分配新的跟踪 ID。这样做会破坏对象与其先前版本的连续性。因此,标识属性必须是重构存储对象的工厂的输入参数之一。
1. An ENTITY FACTORY used for reconstitution does not assign a new tracking ID. To do so would lose the continuity with the object’s previous incarnation. So identifying attributes must be part of the input parameters in a FACTORY reconstituting a stored object.
2. 工厂在重构对象时,对违反不变式的处理方式有所不同。创建新对象时,如果不变式不成立,工厂只需直接终止即可;但在重构过程中,则可能需要更灵活的响应。如果对象已存在于系统中的某个位置(例如数据库中),则不能忽略这一事实。然而,我们也不能忽略规则的违反。必须制定某种策略来修复此类不一致,这使得重构比创建新对象更具挑战性。
2. A FACTORY reconstituting an object will handle violation of an invariant differently. During creation of a new object, a FACTORY should simply balk when an invariant isn’t met, but a more flexible response may be necessary in reconstitution. If an object already exists somewhere in the system (such as in the database), this fact cannot be ignored. Yet we also can’t ignore the rule violation. There has to be some strategy for repairing such inconsistencies, which can make reconstitution more challenging than the creation of new objects.
图 6.16和6.17(下一页)展示了两种重构方式。对象映射技术可以在数据库重构中提供部分或全部此类服务,这非常方便。当从其他介质重构对象时,如果存在明显的复杂性,则 FACTORY是一个不错的选择。
Figures 6.16 and 6.17 (on the next page) show two kinds of reconstitution. Object-mapping technologies may provide some or all of these services in the case of database reconstitution, which is convenient. Whenever there is exposed complexity in reconstituting an object from another medium, the FACTORY is a good option.
图 6.16. 重构从关系数据库中检索到的实体
Figure 6.16. Reconstituting an ENTITY retrieved from a relational database
图 6.17. 重构以 XML 形式传输的实体
Figure 6.17. Reconstituting an ENTITY transmitted as XML
总而言之,必须确定创建实例的访问点,并明确定义其作用域。它们可能只是构造函数,但通常需要更抽象或更具体的定义方式。复杂的实例创建机制。这种需求在设计中引入了新的结构:工厂(Factory)。工厂通常不表达模型的任何部分,但它们是领域设计的一部分,有助于保持表达模型的对象清晰明确。
To sum up, the access points for creation of instances must be identified, and their scope must be defined explicitly. They may simply be constructors, but often there is a need for a more abstract or elaborate instance creation mechanism. This need introduces new constructs into the design: FACTORIES. FACTORIES usually do not express any part of the model, yet they are a part of the domain design that helps keep the model-expressing objects sharp.
工厂(FACTORY)封装了创建和重构的生命周期转换。另一个会暴露技术复杂性并可能淹没领域设计的转换是存储的进出转换。这种转换由另一个领域设计结构——存储库(REPOSITORY)负责。
A FACTORY encapsulates the life cycle transitions of creation and reconstitution. Another transition that exposes technical complexity that can swamp the domain design is the transition to and from storage. This transition is the responsibility of another domain design construct, the REPOSITORY.
关联关系允许我们根据对象与其他对象的关系来查找该对象。但是,要遍历到实体或值生命周期中的某个阶段,我们必须有一个起点。
Associations allow us to find an object based on its relationship to another. But we must have a starting point for a traversal to an ENTITY or VALUE in the middle of its life cycle.
要对对象进行任何操作,都必须持有对它的引用。如何获取这个引用呢?一种方法是创建对象,因为创建操作会返回对新对象的引用。另一种方法是遍历关联关系。从一个已知的对象开始,向它查询一个关联的对象。任何面向对象的程序都会大量使用这种方法,而这些关联关系赋予了对象模型强大的表达能力。但前提是,你必须先获取到第一个对象。
To do anything with an object, you have to hold a reference to it. How do you get that reference? One way is to create the object, as the creation operation will return a reference to the new object. A second way is to traverse an association. You start with an object you already know and ask it for an associated object. Any object-oriented program is going to do a lot of this, and these links give object models much of their expressive power. But you have to get that first object.
我曾经遇到过一个项目,团队热情地拥抱模型驱动设计,试图通过创建或遍历的方式来访问所有对象!他们的对象存储在对象数据库中,他们认为现有的概念关系足以提供所有必要的关联。他们只需要为了充分分析它们,使他们的整个领域模型具有凝聚力,这种自我设限迫使他们创建了我们在前几章中一直试图避免的那种无休止的纠缠,即通过精心实现实体和应用聚合来实现。团队成员并没有长期坚持这种策略,但他们也从未用其他连贯的方法取而代之。他们拼凑了一些临时解决方案,并且变得越来越缺乏雄心壮志。
I actually encountered a project once in which the team was attempting, in an enthusiastic embrace of MODEL-DRIVEN DESIGN, to do all object access by creation or traversal! Their objects resided in an object database, and they reasoned that existing conceptual relationships would provide all necessary associations. They needed only to analyze them enough, making their entire domain model cohesive. This self-imposed limitation forced them to create just the kind of endless tangle that we have been trying to avert over the last few chapters, with careful implementation of ENTITIES and application of AGGREGATES. The team members didn’t stick with this strategy long, but they never replaced it with another coherent approach. They cobbled together ad hoc solutions and became less ambitious.
很少有人会想到这种方法,更不用说被它吸引,因为他们的大部分对象都存储在关系数据库中。这种存储技术使得使用第三种获取引用的方式变得自然而然:根据对象的属性执行查询,在数据库中查找对象;或者找到对象的组成部分,然后将其重新组合。
Few would even think of this approach, much less be tempted by it, because they store most of their objects in relational databases. This storage technology makes it natural to use the third way of getting a reference: Execute a query to find the object in a database based on its attributes, or find the constituents of an object and then reconstitute it.
数据库搜索具有全局可访问性,可以直接访问任何对象。并非所有对象都需要相互关联,这使得对象网络易于管理。是否提供遍历或依赖搜索是一个设计决策,需要在搜索的解耦性和关联的内聚性之间进行权衡。客户对象是否应该包含所有已下订单的集合?或者是否应该通过搜索客户 ID 字段在数据库中查找订单?搜索和关联的正确组合使设计易于理解。
A database search is globally accessible and makes it possible to go directly to any object. There is no need for all objects to be interconnected, which allows us to keep the web of objects manageable. Whether to provide a traversal or depend on a search becomes a design decision, trading off the decoupling of the search against the cohesiveness of the association. Should the Customer object hold a collection of all the Orders placed? Or should the Orders be found in the database, with a search on the Customer ID field? The right combination of search and association makes the design comprehensible.
不幸的是,开发人员通常没有太多时间去考虑这些设计上的细微之处,因为他们忙于处理各种机制,以实现存储对象、将其取回以及最终将其从存储中移除等功能。
Unfortunately, developers don’t usually get to think much about such design subtleties, because they are swimming in the sea of mechanisms needed to pull off the trick of storing an object and bringing it back—and eventually removing it from storage.
从技术角度来看,检索存储的对象实际上是创建对象的一个子集,因为数据库中的数据被用来构建新对象。事实上,通常需要编写的代码也让我们很难忽略这一点。但从概念上讲,这处于实体生命周期的中间阶段。仅仅因为我们将 Customer 对象存储在数据库中并检索了它,并不意味着它就代表了一个新客户。为了强调这种区别,我将从存储的数据创建实例的过程称为重构。
Now from a technical point of view, retrieval of a stored object is really a subset of creation, because the data from the database is used to assemble new objects. Indeed, the code that usually has to be written makes it hard to forget this reality. But conceptually, this is the middle of the life cycle of an ENTITY. A Customer object does not represent a new customer just because we stored it in a database and retrieved it. To keep this distinction in mind, I refer to the creation of an instance from stored data as reconstitution.
领域驱动设计的目标是通过关注领域模型而非技术来创建更好的软件。当开发人员构建 SQL 查询、将其传递给基础架构层的查询服务、获取表行结果集、提取必要信息并将其传递给构造函数或工厂时,模型关注点已不复存在。人们自然而然地将对象视为查询所提供数据的容器,整个设计风格也转向了数据处理模式。技术细节虽各有不同,但问题依然存在:客户端处理的是技术本身,而非模型概念。诸如元数据映射层(Fowler 2003)之类的基础设施通过简化查询结果到对象的转换,极大地改善了这一问题,但开发人员仍然关注技术机制,而非领域知识。更糟糕的是,由于客户端代码直接使用数据库,开发人员很容易绕过聚合等模型特性,甚至绕过对象封装,直接获取并操作所需数据。越来越多的领域规则被嵌入到查询代码中,或者干脆丢失。对象数据库确实消除了转换问题,但搜索机制通常仍然是机械式的,开发人员仍然倾向于获取他们想要的任何对象。
The goal of domain-driven design is to create better software by focusing on a model of the domain rather than the technology. By the time a developer has constructed an SQL query, passed it to a query service in the infrastructure layer, obtained a result set of table rows, pulled the necessary information out, and passed it to a constructor or FACTORY, the model focus is gone. It becomes natural to think of the objects as containers for the data that the queries provide, and the whole design shifts toward a data-processing style. The details of the technology vary, but the problem remains that the client is dealing with technology, rather than model concepts. Infrastructure such as METADATA MAPPING LAYERS (Fowler 2003) help a great deal, by making easier the conversion of the query result into objects, but the developer is still thinking about technical mechanisms, not the domain. Worse, as client code uses the database directly, developers are tempted to bypass model features such as AGGREGATES, or even object encapsulation, instead directly taking and manipulating the data they need. More and more domain rules become embedded in query code or simply lost. Object databases do eliminate the conversion problem, but search mechanisms are usually still mechanistic, and developers are still tempted to grab whatever objects they want.
客户端需要一种便捷的方式来获取对已存在领域对象的引用。如果基础架构使得获取引用变得容易,客户端开发人员可能会添加更多可遍历的关联,从而使模型变得混乱。另一方面,他们可能会使用查询从数据库中提取所需的确切数据,或者提取几个特定的对象,而不是从聚合根节点进行导航。领域逻辑转移到了查询和客户端代码中,实体和值对象则沦为单纯的数据容器。大多数数据库访问基础架构的技术复杂性会迅速淹没客户端代码,导致开发人员简化领域层,最终使模型变得无关紧要。
A client needs a practical means of acquiring references to preexisting domain objects. If the infrastructure makes it easy to do so, the developers of the client may add more traversable associations, muddling the model. On the other hand, they may use queries to pull the exact data they need from the database, or to pull a few specific objects rather than navigating from AGGREGATE roots. Domain logic moves into queries and client code, and the ENTITIES and VALUE OBJECTS become mere data containers. The sheer technical complexity of applying most database access infrastructure quickly swamps the client code, which leads developers to dumb down the domain layer, which makes the model irrelevant.
基于目前讨论的设计原则,我们可以缩小对象访问问题的范围,前提是我们找到一种访问方法,使模型能够保持足够清晰的焦点,从而应用这些原则。首先,我们无需关注瞬态对象。瞬态对象(通常是值对象)生命周期很短,它们在创建它们的客户端操作中使用后就会被丢弃。我们也不需要对持久化对象进行查询访问,因为通过遍历查找更方便。例如,可以直接从 Person 对象请求人员的地址。最重要的是,聚合内部的任何对象都禁止访问,除非从根节点进行遍历。
Drawing on the design principles discussed so far, we can reduce the scope of the object access problem somewhat, assuming that we find a method of access that keeps the model focus sharp enough to employ those principles. For starters, we need not concern ourselves with transient objects. Transients (typically VALUE OBJECTS) live brief lives, used in the client operation that created them and then discarded. We also need no query access for persistent objects that are more convenient to find by traversal. For example, the address of a person could be requested from the Person object. And most important, any object internal to an AGGREGATE is prohibited from access except by traversal from the root.
持久值对象 通常情况下,值是通过遍历某个实体(ENTITY)找到的,该实体作为封装它们的聚合(AGGREGATE)的根。事实上,全局搜索访问值通常是没有意义的,因为通过属性查找值相当于创建一个具有这些属性的新实例。不过也有例外。例如,当我在网上规划旅行时,我有时会保存几个备选行程,稍后再回来选择一个预订。这些行程是值(VALUE)(如果两个行程包含相同的航班,我不会在意哪个是哪个),但它们已经与我的用户名关联,并完整地检索出来。另一种情况是“枚举”,即类型具有严格限制的、预先确定的可能值集合。虽然对值对象进行全局访问远不如对实体进行全局访问常见,但如果您发现需要搜索数据库中已存在的值,则值得考虑是否存在您尚未识别其身份的实体的可能性。
Persistent VALUE OBJECTS are usually found by traversal from some ENTITY that acts as the root of the AGGREGATE that encapsulates them. In fact, a global search access to a VALUE is often meaningless, because finding a VALUE by its properties would be equivalent to creating a new instance with those properties. There are exceptions, though. For example, when I am planning travel online, I sometimes save a few prospective itineraries and return later to select one to book. Those itineraries are VALUES (if there were two made up of the same flights, I would not care which was which), but they have been associated with my user name and retrieved for me intact. Another case would be an “enumeration,” when a type has a strictly limited, predetermined set of possible values. Global access to VALUE OBJECTS is much less common than for ENTITIES, though, and if you find you need to search the database for a preexisting VALUE, it is worth considering the possibility that you’ve really got an ENTITY whose identity you haven’t recognized.
从以上讨论可以看出,大多数对象不应该通过全局搜索访问。设计中最好能明确指出哪些对象需要全局搜索。
From this discussion, it is clear that most objects should not be accessed by a global search. It would be nice for the design to communicate those that do.
现在这个问题可以更精确地重新表述了。
Now the problem can be restated more precisely.
持久化对象子集必须可通过基于对象属性的搜索进行全局访问。这种访问对于聚合的根节点至关重要,因为这些根节点难以通过遍历访问。它们通常是实体,有时是具有复杂内部结构的值对象,有时是枚举值。提供对其他对象的访问会模糊重要的区别。自由数据库查询实际上可能会破坏领域对象和聚合的封装。暴露技术基础架构和数据库访问机制会使客户端变得复杂,并模糊模型驱动设计。
A subset of persistent objects must be globally accessible through a search based on object attributes. Such access is needed for the roots of AGGREGATES that are not convenient to reach by traversal. They are usually ENTITIES, sometimes VALUE OBJECTS with complex internal structure, and sometimes enumerated VALUES. Providing access to other objects muddies important distinctions. Free database queries can actually breach the encapsulation of domain objects and AGGREGATES. Exposure of technical infrastructure and database access mechanisms complicates the client and obscures the MODEL-DRIVEN DESIGN.
有很多方法可以应对数据库访问的技术挑战。例如,可以将 SQL 封装到查询对象 (QUERY OJECTS)中,或者使用元数据映射层 ( METADATA MAPPING LAYERS)在对象和表之间进行转换(Fowler 2003)。工厂 (FACTORIES)可以帮助重构存储的对象(本章稍后将讨论)。这些以及许多其他技术有助于控制复杂性。
There is a raft of techniques for dealing with the technical challenges of database access. Examples include encapsulating SQL into QUERY OBJECTS or translating between objects and tables with METADATA MAPPING LAYERS (Fowler 2003). FACTORIES can help reconstitute stored objects (as discussed later in this chapter). These and many other techniques help keep a lid on complexity.
但即便如此,也要注意我们失去了什么。我们不再在领域模型中思考概念。我们的代码不再与业务进行沟通,而是会操控技术。数据检索。存储库模式是一个简单的概念框架,用于封装这些解决方案,并将我们的注意力重新集中到模型上。
But even so, take note of what has been lost. We are no longer thinking about concepts in our domain model. Our code will not be communicating about the business; it will be manipulating the technology of data retrieval. The REPOSITORY pattern is a simple conceptual framework to encapsulate those solutions and bring back our model focus.
存储库(REPOSITORY)将特定类型的所有对象表示为一个概念集合(通常是模拟的)。它类似于集合,但具有更强大的查询功能。相应类型的对象会被添加和移除,存储库背后的机制会将它们插入或删除到数据库中。此定义汇总了从生命周期早期到结束,为聚合(AGGREGATE)的根节点提供访问的一系列职责。
A REPOSITORY represents all objects of a certain type as a conceptual set (usually emulated). It acts like a collection, except with more elaborate querying capability. Objects of the appropriate type are added and removed, and the machinery behind the REPOSITORY inserts them or deletes them from the database. This definition gathers a cohesive set of responsibilities for providing access to the roots of AGGREGATES from early life cycle through the end.
客户端使用查询方法从存储库 (REPOSITORY)请求对象,这些查询方法根据客户端指定的条件(通常是某些属性的值)选择对象。存储库检索请求的对象,并封装了数据库查询和元数据映射的机制。存储库可以实现各种查询,根据客户端所需的任何条件选择对象。它们还可以返回汇总信息,例如满足某些条件的实例数量。它们甚至可以返回汇总计算结果,例如某个数值属性的所有匹配对象的总和。
Clients request objects from the REPOSITORY using query methods that select objects based on criteria specified by the client, typically the value of certain attributes. The REPOSITORY retrieves the requested object, encapsulating the machinery of database queries and metadata mapping. REPOSITORIES can implement a variety of queries that select objects based on whatever criteria the client requires. They can also return summary information, such as a count of how many instances meet some criteria. They can even return summary calculations, such as the total across all matching objects of some numerical attribute.
图 6.18.存储库正在搜索客户端
Figure 6.18. A REPOSITORY doing a search for a client
存储库大大减轻了客户端的负担,客户端现在可以通过一个简洁明了、意图明确的接口进行交互,并根据模型请求所需的内容。虽然支持这一切需要大量复杂的技术基础设施,但该接口本身简单易用,并且在概念上与领域模型紧密相连。
A REPOSITORY lifts a huge burden from the client, which can now talk to a simple, intention-revealing interface, and ask for what it needs in terms of the model. To support all this requires a lot of complex technical infrastructure, but the interface is simple and conceptually connected to the domain model.
所以:
Therefore:
对于每种需要全局访问的对象类型,创建一个对象,该对象能够提供内存中所有此类对象的集合的假象。通过一个众所周知的全局接口设置访问。提供添加和删除对象的方法,这些方法将封装数据存储中实际的数据插入或删除操作。提供基于特定条件选择对象的方法,并返回属性值符合条件的完全实例化对象或对象集合,从而封装实际的存储和查询技术。仅为真正需要直接访问的聚合根提供存储库。让客户端专注于模型,将所有对象存储和访问委托给存储库。
For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type. Set up access through a well-known global interface. Provide methods to add and remove objects, which will encapsulate the actual insertion or removal of data in the data store. Provide methods that select objects based on some criteria and return fully instantiated objects or collections of objects whose attribute values meet the criteria, thereby encapsulating the actual storage and query technology. Provide REPOSITORIES only for AGGREGATE roots that actually need direct access. Keep the client focused on the model, delegating all object storage and access to the REPOSITORIES.
存储库具有诸多优势,包括以下几点:
REPOSITORIES have many advantages, including the following:
• 他们向客户展示了一个简单的模型,用于获取持久对象并管理其生命周期。
• They present clients with a simple model for obtaining persistent objects and managing their life cycle.
• 它们将应用程序和领域设计与持久化技术、多种数据库策略,甚至多种数据源解耦。
• They decouple application and domain design from persistence technology, multiple database strategies, or even multiple data sources.
• 它们用于沟通有关对象访问的设计决策。
• They communicate design decisions about object access.
• 它们允许轻松替换虚拟实现,以便在测试中使用(通常使用内存集合)。
• They allow easy substitution of a dummy implementation, for use in testing (typically using an in-memory collection).
所有存储库都提供了允许客户端请求符合某些条件的对象的方法,但设计此接口的方式有很多种。
All repositories provide methods that allow a client to request objects matching some criteria, but there is a range of options of how to design this interface.
最容易构建的存储库包含带有特定参数的硬编码查询。这些查询多种多样:通过标识检索实体(几乎所有存储库都提供此功能);请求具有特定属性值或复杂参数组合的对象集合;基于值范围(例如日期范围)选择对象;甚至执行一些属于存储库一般职责范围内的计算(特别是利用底层数据库支持的操作)。
The easiest REPOSITORY to build has hard-coded queries with specific parameters. These queries can be various: retrieving an ENTITY by its identity (provided by almost all REPOSITORIES); requesting a collection of objects with a particular attribute value or a complex combination of parameters; selecting objects based on value ranges (such as date ranges); and even performing some calculations that fall within the general responsibility of a REPOSITORY (especially drawing on operations supported by the underlying database).
虽然大多数查询返回一个对象或一组对象,但返回某些类型的汇总计算结果也符合这一概念,例如对象计数,或模型预期要统计的数值属性的总和。
Although most queries return an object or a collection of objects, it also fits within the concept to return some types of summary calculations, such as an object count, or a sum of a numerical attribute that was intended by the model to be tallied.
图 6.19. 简单存储库中的硬编码查询
Figure 6.19. Hard-coded queries in a simple REPOSITORY
硬编码查询可以构建在任何基础设施之上,而且无需大量投资,因为它们所做的正是某些客户无论如何都必须做的事情。
Hard-coded queries can be built on top of any infrastructure and without a lot of investment, because they do just what some client would have had to do anyway.
对于查询量较大的项目,可以构建一个存储库框架,以实现更灵活的查询。这需要一支熟悉相关技术的团队,并且需要完善的基础设施支持。
On projects with a lot of querying, a REPOSITORY framework can be built that allows more flexible queries. This calls for a staff familiar with the necessary technology and is greatly aided by a supportive infrastructure.
一种通过框架概括REPOSITORIES的特别合适的方法是使用基于规范的查询。规范允许客户端描述(即指定)其所需内容,而无需关心如何获取。在此过程中,会创建一个能够实际执行选择的对象。此模式将在第 9 章中深入讨论。
One particularly apt approach to generalizing REPOSITORIES through a framework is to use SPECIFICATION-based queries. A SPECIFICATION allows a client to describe (that is, specify) what it wants without concern for how it will be obtained. In the process, an object that can actually carry out the selection is created. This pattern will be discussed in depth in Chapter 9.
图 6.20. 复杂存储库中搜索条件的灵活声明式规范
Figure 6.20. A flexible, declarative SPECIFICATION of search criteria in a sophisticated REPOSITORY
基于规范的查询既简洁又灵活。根据可用基础设施的不同,它可能是一个简易的框架,也可能极其复杂。Rob Mee 和 Edward Hieatt 在 Fowler 2003 年的论文中讨论了设计此类存储库所涉及的更多技术问题。
The SPECIFICATION-based query is elegant and flexible. Depending on the infrastructure available, it may be a modest framework or it may be prohibitively difficult. Rob Mee and Edward Hieatt discuss more of the technical issues involved in designing such REPOSITORIES in Fowler 2003.
即使是具有灵活查询功能的存储库设计也应该允许添加专门的硬编码查询。这些查询可以是封装常用查询的便捷方法,也可以是不返回对象本身的查询,例如数学运算。所选对象的摘要。不允许此类意外情况发生的框架往往会扭曲领域设计,或者被开发人员绕过。
Even a REPOSITORY design with flexible queries should allow for the addition of specialized hard-coded queries. They might be convenience methods that encapsulate an often-used query or a query that doesn’t return the objects themselves, such as a mathematical summary of selected objects. Frameworks that don’t allow for such contingencies tend to distort the domain design or get bypassed by developers.
持久化技术的封装使得客户端非常简单,完全与存储库的实现解耦。但正如封装通常存在的问题一样,开发人员必须了解底层的工作原理。当存储库以不同的方式使用或工作时,性能影响可能非常显著。
Encapsulation of the persistence technology allows the client to be very simple, completely decoupled from the implementation of the REPOSITORY. But as is often the case with encapsulation, the developer must understand what is happening under the hood. The performance implications can be extreme when REPOSITORIES are used in different ways or work in different ways.
凯尔·布朗给我讲了他被叫去处理一个基于 WebSphere 的制造应用的故事,这个应用当时正在部署到生产环境中。系统运行几个小时后,就莫名其妙地内存不足了。凯尔浏览了代码,找到了原因:在某个地方,他们正在汇总工厂中每个物料的信息。开发人员使用了一个名为“所有对象”的查询,该查询会实例化每个对象,然后选择所需的部分。这段代码的结果是,整个数据库一次性加载到了内存中!由于测试数据量较小,这个问题在测试阶段没有显现出来。
Kyle Brown told me the story of getting called in on a manufacturing application based on WebSphere that was being rolled out to production. The system was mysteriously running out of memory after a few hours of use. Kyle browsed through the code and found the reason: At one point, they were summarizing some information about every item in the plant. The developers had done this using a query called “all objects,” which instantiated each of the objects and then selected the bits they needed. This code had the effect of bringing the entire database into memory at once! The problem hadn’t shown up in testing because of the small amount of test data.
这显然是禁忌,但一些更为隐蔽的疏忽也会带来同样严重的问题。开发人员需要理解使用封装行为的后果。这并不意味着他们需要对实现细节了如指掌。设计良好的组件是可以进行特征描述的。(这是第 10 章“灵活设计”的主要内容之一。)
This is an obvious no-no, but much more subtle oversights can present equally serious problems. Developers need to understand the implications of using encapsulated behavior. That does not have to mean detailed familiarity with the implementation. Well-designed components can be characterized. (This is one of the main points of Chapter 10, “Supple Design.”)
正如第五章所述,底层技术可能会限制您的建模选择。例如,关系数据库可能会对深度组合对象结构造成实际限制。同样,在使用存储库和实现其查询之间,必须建立双向反馈机制,以便开发人员能够及时了解情况。
As was discussed in Chapter 5, the underlying technology may constrain your modeling choices. For example, a relational database can place a practical limit on deep compositional object structures. In just the same way, there must be feedback to developers in both directions between the use of the REPOSITORY and the implementation of its queries.
实现方式会因持久化技术和现有基础设施的不同而有很大差异。理想情况下,应该对所有内部运作机制向客户端隐藏(但客户端开发者除外),这样无论数据存储在对象数据库、关系数据库还是内存中,客户端代码都将保持一致。存储库(REPOSITORY)会将任务委托给相应的基础设施服务来完成。封装存储、检索和查询机制是存储库实现的最基本特性。
Implementation will vary greatly, depending on the technology being used for persistence and the infrastructure you have. The ideal is to hide all the inner workings from the client (although not from the developer of the client), so that client code will be the same whether the data is stored in an object database, stored in a relational database, or simply held in memory. The REPOSITORY will delegate to the appropriate infrastructure services to get the job done. Encapsulating the mechanisms of storage, retrieval, and query is the most basic feature of a REPOSITORY implementation.
图 6.21. REPOSITORY封装了底层数据存储。
Figure 6.21. The REPOSITORY encapsulates the underlying data store.
REPOSITORY的概念适用于多种情况。其实现方式多种多样,我只能列出一些需要注意的事项。
The REPOSITORY concept is adaptable to many situations. The possibilities of implementation are so diverse that I can only list some concerns to keep in mind.
•抽象类型。一个存储库“包含”特定类型的所有实例,但这并不意味着每个类都需要一个存储库。该类型可以是层次结构的抽象超类(例如,交易订单可以是买入订单或卖出订单)。该类型可以是接口,其实现者之间甚至在层次结构上都无关。或者,它可以是一个具体的类。请注意,由于您的数据库技术缺乏这种多态性,您可能会面临一些限制。
• Abstract the type. A REPOSITORY “contains” all instances of a specific type, but this does not mean that you need one REPOSITORY for each class. The type could be an abstract superclass of a hierarchy (for example, a TradeOrder could be a BuyOrder or a Sell-Order). The type could be an interface whose implementers are not even hierarchically related. Or it could be a specific concrete class. Keep in mind that you may well face constraints imposed by the lack of such polymorphism in your database technology.
•利用与客户端的解耦优势。与客户端直接调用机制相比,您可以更自由地更改存储库的实现。您可以利用这一点来优化性能,例如改变查询技术或在内存中缓存对象,并随时自由切换持久化策略。您可以通过提供易于操作的虚拟内存策略来简化客户端代码和领域对象的测试。
• Take advantage of the decoupling from the client. You have more freedom to change the implementation of a REPOSITORY than you would if the client were calling the mechanisms directly. You can take advantage of this to optimize for performance, by varying the query technique or by caching objects in memory, freely switching persistence strategies at any time. You can facilitate testing of the client code and the domain objects by providing an easily manipulated, dummy in-memory strategy.
•将事务控制权交给客户端。虽然存储库会向数据库插入和删除数据,但通常不会提交任何内容。例如,在保存后提交数据看似诱人,但客户端理应具备正确启动和提交工作单元的上下文。如果存储库不干预事务,事务管理将会更加简单。
• Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.
通常,团队会在基础设施层添加一个框架来支持REPOSITORY的实现。除了与底层基础设施组件协作之外,REPOSITORY超类可能还会实现一些基本查询,尤其是在实现灵活查询时。遗憾的是,对于像 Java 这样的类型系统,这种方法会强制将返回的对象类型设置为“Object”,从而需要客户端将其强制转换为REPOSITORY包含的类型。当然,在 Java 中,对于返回集合的查询,无论如何都需要进行这种强制转换。
Typically teams add a framework to the infrastructure layer to support the implementation of REPOSITORIES. In addition to the collaboration with the lower level infrastructure components, the REPOSITORY superclass might implement some basic queries, especially when a flexible query is being implemented. Unfortunately, with a type system such as Java’s, this approach would force you to type returned objects as “Object,” leaving the client to cast them to the REPOSITORY’S contained type. But of course, this will have to be done with queries that return collections anyway in Java.
有关实施REPOSITORIES及其一些支持的技术模式(例如QUERY OBJECT )的一些额外指导,可以在 Fowler (2003) 中找到。
Some additional guidance on implementing REPOSITORIES and some of their supporting technical patterns such as QUERY OBJECT can be found in Fowler (2003).
在实现类似仓库(REPOSITORY)的功能之前,您需要仔细考虑您所依赖的基础架构,尤其是任何架构框架。您可能会发现该框架提供了一些服务,方便您轻松创建仓库,也可能会发现该框架会处处阻挠您。您甚至可能会发现该架构框架已经定义了等效的功能。获取持久化对象的模式。或者您可能会发现,它定义的模式根本不像存储库。
Before implementing something like a REPOSITORY, you need to think carefully about the infrastructure you are committed to, especially any architectural frameworks. You may find that the framework provides services you can use to easily create a REPOSITORY, or you may find that the framework fights you all the way. You may discover that the architectural framework has already defined an equivalent pattern of getting persistent objects. Or you may discover that it has defined a pattern that is not like a REPOSITORY at all.
例如,您的项目可能基于 J2EE。为了寻找框架与模型驱动设计模式之间的概念契合点(同时请记住,实体 Bean与实体并非同一概念),您可能选择使用实体 Bean 来对应聚合根。J2EE 架构框架中负责提供对这些对象访问的结构是“EJB Home”。试图将 EJB Home 伪装成存储库可能会导致其他问题。
For example, your project might be committed to J2EE. Looking for conceptual affinities between the framework and the patterns of MODEL-DRIVEN DESIGN (and keeping in mind that an entity bean is not the same thing as an ENTITY), you may have chosen to use entity beans to correspond to AGGREGATE roots. The construct within the architectural framework of J2EE that is responsible for providing access to these objects is the “EJB Home.” Trying to dress up the EJB Home to look like a REPOSITORY could lead to other problems.
一般来说,不要与框架对抗。当框架与领域驱动设计的基本原则相冲突时,应寻求保留这些基本原则的方法,并放弃一些具体的细节。寻找领域驱动设计概念与框架概念之间的契合点。当然,前提是你别无选择,只能使用该框架。许多 J2EE 项目根本不使用实体 Bean。如果可以自由选择,请选择与你想要的设计风格相协调的框架或框架组件。
In general, don’t fight your frameworks. Seek ways to keep the fundamentals of domain-driven design and let go of the specifics when the framework is antagonistic. Look for affinities between the concepts of domain-driven design and the concepts in the framework. This is assuming that you have no choice but to use the framework. Many J2EE projects don’t use entity beans at all. If you have the freedom, choose frameworks, or parts of frameworks, that are harmonious with the style of design you want to use.
工厂负责处理对象生命周期的开始;仓库则负责管理对象的生命周期中期和结束。当对象保存在内存中或存储在对象数据库中时,这很简单。但通常情况下,对象至少会以某种形式存储在关系数据库、文件或其他非面向对象的系统中。在这种情况下,检索到的数据必须重新构建成对象的形式。
A FACTORY handles the beginning of an object’s life; a REPOSITORY helps manage the middle and the end. When objects are being held in memory, or stored in an object database, this is straightforward. But typically there is at least some object storage in relational databases, files, or other, non-object-oriented systems. In such cases, the retrieved data must be reconstituted into object form.
由于在这种情况下,存储库(REPOSITORY)会基于数据创建对象,因此许多人将其视为工厂( FACTORY )——从技术角度来看,的确如此。但更重要的是将模型置于首位,而且如前所述,重构已存储的对象并非创建新的概念对象。在这种领域驱动的设计视角下,工厂和存储库的职责截然不同。工厂创建新对象;存储库查找旧对象。存储库的客户端应该感觉对象仍然存在于内存中。对象可能需要重构(是的,可能会创建一个新的实例),但它仍然是同一个概念对象,仍然处于其生命周期的中间阶段。
Because the REPOSITORY is, in this case, creating objects based on data, many people consider the REPOSITORY to be a FACTORY—indeed it is, from a technical point of view. But it is more useful to keep the model in the forefront, and as mentioned before, the reconstitution of a stored object is not the creation of a new conceptual object. In this domain-driven view of the design, FACTORIES and REPOSITORIES have distinct responsibilities. The FACTORY makes new objects; the REPOSITORY finds old objects. The client of a REPOSITORY should be given the illusion that the objects are in memory. The object may have to be reconstituted (yes, a new instance may be created), but it is the same conceptual object, still in the middle of its life cycle.
这两种观点可以通过让REPOSITORY 将对象创建委托给FACTORY来调和,FACTORY(理论上,尽管在实践中很少使用)也可以用于从头开始创建对象。
These two views can be reconciled by making the REPOSITORY delegate object creation to a FACTORY, which (in theory, though seldom in practice) could also be used to create objects from scratch.
图 6.22. REPOSITORY使用FACTORY来重新构建预先存在的对象。
Figure 6.22. A REPOSITORY uses a FACTORY to reconstitute a preexisting object.
这种清晰的分离还有助于将所有持久化的责任从工厂(Factory)中剥离出来。工厂的任务是根据数据实例化一个可能很复杂的对象。如果产品是一个新对象,客户端会知道这一点,并将其添加到存储库(Repository)中,存储库将封装该对象在数据库中的存储。
This clear separation also helps by unloading all responsibility for persistence from the FACTORIES. A FACTORY’S job is to instantiate a potentially complex object from data. If the product is a new object, the client will know this and can add it to the REPOSITORY, which will encapsulate the storage of the object in the database.
图 6.23. 客户端使用REPOSITORY存储新对象。
Figure 6.23. A client uses a REPOSITORY to store a new object.
促使人们将FACTORY和REPOSITORY结合使用的另一个原因是需要“查找或创建”功能,即客户端可以描述它想要的对象,如果找不到该对象,则会创建一个新对象。应该避免使用此功能。它充其量只是一个小小的便利。当实体对象和值对象被区分后,许多看似有用的场景就消失了。需要值对象的客户端可以直接向工厂请求创建一个新对象。通常,区分新对象和现有对象在领域内非常重要,而一个透明地将它们合并在一起的框架实际上会使情况更加混乱。
One other case that drives people to combine FACTORY and REPOSITORY is the desire for “find or create” functionality, in which a client can describe an object it wants and, if no such object is found, will be given a newly created one. This function should be avoided. It is a minor convenience at best. A lot of cases in which it seems useful go away when ENTITIES and VALUE OBJECTS are distinguished. A client that wants a VALUE OBJECT can go straight to a FACTORY and ask for a new one. Usually, the distinction between a new object and an existing object is important in the domain, and a framework that transparently combines them will actually muddle the situation.
在以面向对象为主的软件系统中,最常见的非对象组件是关系数据库。这种现实带来了混合范式带来的常见问题(参见第五章)。但与其他大多数组件相比,数据库与对象模型的关系更为密切。数据库不仅与对象交互,还存储构成对象本身的持久化数据。关于将对象映射到关系表以及如何有效地存储和检索这些关系表的技术挑战,已有大量文献论述。Fowler (2003) 对此进行了最新的探讨。目前已有相当完善的工具用于创建和管理两者之间的映射。除了技术问题之外,这种不匹配还会对对象模型产生重大影响。
The most common nonobject component of primarily object-oriented software systems is the relational database. This reality presents the usual problems of a mixture of paradigms (see Chapter 5). But the database is more intimately related to the object model than are most other components. The database is not just interacting with the objects; it is storing the persistent form of the data that makes up the objects themselves. A good deal has been written about the technical challenges of mapping objects to relational tables and effectively storing and retrieving them. A recent discussion can be found in Fowler 2003. There are reasonably refined tools for creating and managing mappings between the two. Apart from the technical concerns, this mismatch can have a significant impact on the object model.
常见情况有三种:
There are three common cases:
1.数据库主要是一个对象存储库。
1. The database is primarily a repository for the objects.
2.该数据库是为另一个系统设计的。
2. The database was designed for another system.
3.该数据库是为该系统设计的,但除了对象存储之外,还承担其他角色。
3. The database is designed for this system but serves in roles other than object store.
当数据库模式专门用于存储对象时,为了保持映射的简洁性,接受一些模型限制是值得的。在没有其他模式设计要求的情况下,数据库可以构建成在更新时能够更安全、更高效地维护聚合完整性的结构。从技术上讲,关系表的设计不必反映领域模型。映射工具足够强大,可以弥合显著的差异。问题在于,多个重叠的模型过于复杂。许多支持模型驱动设计(MDD)的论点——避免使用单独的分析模型和设计模型——同样适用于这种不匹配的情况。这确实意味着在对象模型的丰富性方面做出一些牺牲,有时需要在数据库中做出一些妥协。设计(例如选择性反规范化)是可行的,但否则则可能失去模型与实现之间的紧密耦合。这种方法并不需要简单的单对象/单表映射。根据映射工具的功能,可以进行一些对象聚合或组合。但至关重要的是,映射必须透明,可以通过检查代码或阅读映射工具中的条目轻松理解。
When the database schema is being created specifically as a store for the objects, it is worth accepting some model limitations in order to keep the mapping very simple. Without other demands on schema design, the database can be structured to make aggregate integrity safer and more efficient as updates are made. Technically, the relational table design does not have to reflect the domain model. Mapping tools are sophisticated enough to bridge significant differences. The trouble is, multiple overlapping models are just too complicated. Many of the same arguments presented for MODEL-DRIVEN DESIGN—avoiding separate analysis and design models—apply to this mismatch. This does entail some sacrifice in the richness of the object model, and sometimes compromises have to be made in the database design (such as selective denormalization), but to do otherwise is to risk losing the tight coupling of model and implementation. This approach doesn’t require a simplistic one-object/one-table mapping. Depending on the power of the mapping tool, some aggregation or composition of objects may be possible. But it is crucial that the mappings be transparent, easily understandable by inspecting the code or reading entries in the mapping tool.
• 当数据库被视为对象存储时,无论映射工具多么强大,都不要让数据模型和对象模型偏离太远。为了贴近关系模型,可以牺牲一些对象关系的丰富性。如果能够简化对象映射,可以适当妥协一些正式的关系标准,例如规范化。
• When the database is being viewed as an object store, don’t let the data model and the object model diverge far, regardless of the powers of the mapping tools. Sacrifice some richness of object relationships to keep close to the relational model. Compromise some formal relational standards, such as normalization, if it helps simplify the object mapping.
• 对象系统之外的进程不应访问此类对象存储。它们可能会违反对象强制执行的不变性。此外,它们的访问会锁定数据模型,导致在重构对象时难以更改。
• Processes outside the object system should not access such an object store. They could violate the invariants enforced by the objects. Also, their access will lock in the data model so that it is hard to change when the objects are refactored.
另一方面,很多情况下,数据来自遗留系统或外部系统,而这些系统最初并非设计用于存储对象。在这种情况下,实际上同一个系统中存在两个领域模型共存的情况。第 14 章“维护模型完整性”对此问题进行了深入探讨。或许可以遵循另一个系统中隐含的模型,但也可能最好创建一个完全不同的模型。
On the other hand, there are many cases in which the data comes from a legacy or external system that was never intended as a store of objects. In this situation, there are, in reality, two domain models coexisting in the same system. Chapter 14, “Maintaining Model Integrity,” deals with this issue in depth. It may make sense to conform to the model implicit in the other system, or it may be better to make the model completely distinct.
出现例外情况的另一个原因是性能问题。为了解决执行速度问题,可能需要引入一些特殊的设计变更。
Another reason for exceptions is performance. Quirky design changes may have to be introduced to solve execution speed problems.
但对于关系数据库作为面向对象领域的持久化形式这种重要的常见情况而言,简单直接才是最佳选择。表中的一行应该包含一个对象,或许还可以包含聚合(AGGREGATE)中的子对象。表中的外键应该指向另一个实体(ENTITY)对象。有时需要偏离这种简单直接的原则,但这并不意味着要完全放弃简单映射的原则。
But for the important common case of a relational database acting as the persistent form of an object-oriented domain, simple directness is best. A table row should contain an object, perhaps along with subsidiaries in an AGGREGATE. A foreign key in the table should translate to a reference to another ENTITY object. The necessity of sometimes deviating from this simple directness should not lead to total abandonment of the principle of simple mappings.
通用语言有助于将对象和关系组件整合到一个单一的模型中。对象中元素的名称和关联关系应该与……一丝不苟地对应。这些关系表。虽然某些映射工具的强大功能可能使这看起来没有必要,但关系中细微的差别会造成很多混乱。
The UBIQUITOUS LANGUAGE can help tie the object and relational components together to a single model. The names and associations of elements in the objects should correspond meticulously to those of the relational tables. Although the power of some mapping tools may make this seem unnecessary, subtle differences in relationships will cause a lot of confusion.
重构的传统在对象编程领域日益盛行,但对关系型数据库设计的影响却并不显著。此外,严重的数据迁移问题也阻碍了频繁的变更。这可能会拖慢对象模型的重构进程,但如果对象模型和数据库模型开始出现分歧,透明度就会迅速丧失。
The tradition of refactoring that has increasingly taken hold in the object world has not really affected relational database design much. What’s more, serious data migration issues discourage frequent change. This may create a drag on the refactoring of the object model, but if the object model and the database model start to diverge, transparency can be lost quickly.
最后,即使数据库是专门为你的系统创建的,也有一些理由选择与对象模型截然不同的模式。数据库可能还会被其他不会实例化对象的软件使用。即使对象的行为快速变化或演进,数据库可能也只需要很小的改动。将两者完全割裂开来固然诱人,但当团队未能使数据库与模型保持同步时,这种情况往往会在无意中发生。如果这种分离是出于有意识的选择,那么就能得到一个清晰的数据库模式,而不是一个充斥着妥协、勉强符合去年对象模型的尴尬模式。
Finally, there are some reasons to go with a schema that is quite distinct from the object model, even when the database is being created specifically for your system. The database may also be used by other software that will not instantiate objects. The database may require little change, even while the behavior of the objects changes or evolves rapidly. Cutting the two loose from each other is a seductive path. It is often taken unintentionally, when the team fails to keep the database current with the model. If the separation is chosen consciously, it can result in a clean database schema—not an awkward one full of compromises conforming to last year’s object model.
前三章介绍了一种模式语言,用于精细化模型并保持严格的模型驱动设计。在之前的示例中,这些模式大多是逐一应用的,但在实际项目中,必须将它们结合起来使用。本章将提供一个精心设计的示例(当然,它仍然比实际项目简单得多)。该示例将逐步展示一个假想团队在处理需求和实现问题并开发模型驱动设计的过程中,如何进行一系列的模型和设计改进,从而揭示其中存在的各种因素以及第二部分中的模式如何解决这些问题。
The preceding three chapters introduced a pattern language for honing the fine detail of a model and maintaining a tight MODEL-DRIVEN DESIGN. In the earlier examples, the patterns were mostly applied one at a time, but on a real project you have to combine them. This chapter presents one elaborate example (still drastically simpler than a real project, of course). The example will step through a succession of model and design refinements as a hypothetical team deals with requirements and implementation issues and develops a MODEL-DRIVEN DESIGN, showing the forces that apply and how the patterns of Part II can resolve them.
我们正在为一家货运公司开发新软件。最初的需求是三个基本功能。
We’re developing new software for a cargo shipping company. The initial requirements are three basic functions.
1.跟踪客户货物的关键处理情况
1. Track key handling of customer cargo
2.提前预订货物
2. Book cargo in advance
3.当货物在处理过程中到达某个环节时,自动向客户发送发票。
3. Send invoices to customers automatically when the cargo reaches some point in its handling
在实际项目中,需要花费一些时间和反复迭代才能最终明确这个模型。本书第三部分将深入探讨这一探索过程。但在这里,我们将从一个具有以下特征的模型开始:我们将以合理的形式提出所需的概念,并专注于微调细节以支持设计。
In a real project, it would take some time and iteration to get to the clarity of this model. Part III of this book will go into the discovery process in depth. But here we’ll start with a model that has the needed concepts in a reasonable form, and we’ll focus on fine-tuning the details to support design.
图 7.1. 表示航运领域模型的类图
Figure 7.1. A class diagram representing a model of the shipping domain
该模型组织了领域知识,并为团队提供了一种通用语言。我们可以这样表述:
This model organizes domain knowledge and provides a language for the team. We can make statements like this:
“多位客户参与同一货物运输,每位客户都扮演着不同的角色。”
“Multiple Customers are involved with a Cargo, each playing a different role.”
“货物交付目标已明确。”
“The Cargo delivery goal is specified.”
“一系列符合规范的运输作业将实现交付目标。”
“A series of Carrier Movements satisfying the Specification will fulfill the delivery goal.”
模型中的每个对象都有明确的含义:
Each object in the model has a clear meaning:
装卸事件是指对货物采取的离散操作,例如装船或清关。此类事件可能会进一步细分为不同类型的事件层级,例如装货、卸货或收货人认领。
A Handling Event is a discrete action taken with the Cargo, such as loading it onto a ship or clearing it through customs. This class would probably be elaborated into a hierarchy of different kinds of incidents, such as loading, unloading, or being claimed by the receiver.
交付规格 定义交付目标,至少应包括目的地和到达日期,但也可以更复杂。此类遵循SPECIFICATION模式(参见第 9 章)。
Delivery Specification defines a delivery goal, which at minimum would include a destination and an arrival date, but it can be more complex. This class follows the SPECIFICATION pattern (see Chapter 9).
货物对象本可以承担这项责任,但交付规范的抽象至少带来了三个优点。
This responsibility could have been taken on by the Cargo object, but the abstraction of Delivery Specification gives at least three advantages.
1.如果没有交付规范,货物对象将负责详细说明所有用于指定交付目标的属性和关联关系。这将使货物对象变得臃肿,难以理解和修改。
1. Without Delivery Specification, the Cargo object would be responsible for the detailed meaning of all those attributes and associations for specifying the delivery goal. This would clutter up Cargo and make it harder to understand or change.
2.这种抽象使得在解释整个模型时可以轻松安全地省略细节。例如,交付规范中可能包含其他标准,但这种级别的图表无需将其展现出来。该图表旨在告诉读者存在交付规范,而其细节无需考虑(实际上,这些细节以后很容易更改)。
2. This abstraction makes it easy and safe to suppress detail when explaining the model as a whole. For example, there could be other criteria encapsulated in the Delivery Specification, but a diagram at this level of detail would not have to expose it. The diagram is telling the reader that there is a SPECIFICATION of delivery, and the details of that are not important to think about (and, in fact, could be easily changed later).
3.该模型更具表达力。添加交付规范明确指出,货物的具体交付方式尚未确定,但必须实现交付规范中规定的目标。
3. This model is more expressive. Adding Delivery Specification says explicitly that the exact means of delivery of the Cargo is undetermined, but that it must accomplish the goal set out in the Delivery Specification.
角色用于区分客户在货运过程中扮演的不同角色。例如,“发货人”、“收货人”、“付款人”等等。由于特定货物只能由一位客户扮演特定角色,因此这种关联关系是限定的多对一关系,而非多对多关系。角色可以简单地实现为字符串,如果需要其他行为,也可以实现为类。
A role distinguishes the different parts played by Customers in a shipment. One is the “shipper,” one the “receiver,” one the “payer,” and so on. Because only one Customer can play a given role for a particular Cargo, the association becomes a qualified many-to-one instead of many-to-many. Role might be implemented as simply a string, or it could be a class if other behavior is needed.
运输运输是指特定运输工具(例如卡车或船舶)从一个地点到另一个地点的单次运输行程。货物可以通过装载到运输工具上,在一次或多次运输运输过程中从一个地点运送到另一个地点。
Carrier Movement represents one particular trip by a particular Carrier (such as a truck or a ship) from one Location to another. Cargoes can ride from place to place by being loaded onto Carriers for the duration of one or more Carrier Movements.
交付历史记录反映的是货物实际发生的情况,而非描述目标的交付规范。交付历史记录对象可以通过分析上次装卸货以及相应承运商运输的目的地来计算货物的当前位置。一次成功的交付将以交付历史记录结束。 满足交付规范的目标。
Delivery History reflects what has actually happened to a Cargo, as opposed to the Delivery Specification, which describes goals. A Delivery History object can compute the current Location of the Cargo by analyzing the last load or unload and the destination of the corresponding Carrier Movement. A successful delivery would end with a Delivery History that satisfied the goals of the Delivery Specification.
该模型包含了满足上述需求所需的所有概念,并假设存在适当的机制来持久化对象、查找相关对象等等。这些实现问题在模型中并未涉及,但必须在设计阶段加以解决。
All the concepts needed to work through the requirements just described are present in this model, assuming appropriate mechanisms to persist the objects, find the relevant objects, and so on. Such implementation issues are not dealt with in the model, but they must be in the design.
为了构建一个切实可行的实施方案,该模型仍需进一步澄清和完善。
In order to frame up a solid implementation, this model still needs some clarification and tightening.
通常情况下,模型的完善、设计和实现应该在迭代开发过程中齐头并进。但为了便于解释,本章将从一个相对成熟的模型入手,所有修改都将严格出于将该模型与实际实现相结合的需要,并采用构建模块模式。
Remember, ordinarily, model refinement, design, and implementation should go hand-in-hand in an iterative development process. But in this chapter, for clarity of explanation, we are starting with a relatively mature model, and changes will be motivated strictly by the need to connect that model with a practical implementation, employing the building block patterns.
通常情况下,为了更好地支持设计,模型在不断完善的过程中也应该根据对该领域的新见解进行改进。但在本章中,为了清晰地解释,所有修改都将严格基于与实际应用相结合的需要,并采用构建模块模式。
Ordinarily, as the model is being refined to support the design better, it should also be refined to reflect new insight into the domain. But in this chapter, for clarity of explanation, changes will be strictly motivated by the need to connect with a practical implementation, employing the building block patterns.
为了防止领域职责与系统其他部分的职责混淆,让我们应用分层架构来划分领域层。
To prevent domain responsibilities from being mixed with those of other parts of the system, let’s apply LAYERED ARCHITECTURE to mark off a domain layer.
无需深入分析,我们就能确定三个用户级应用程序功能,并将它们分配给三个应用程序层类。
Without going into deep analysis, we can identify three user-level application functions, which we can assign to three application layer classes.
1.跟踪查询,可访问特定货物的过往和当前处理情况。
1. A Tracking Query that can access past and present handling of a particular Cargo
2.一个允许注册新货物并为此做好系统准备的预订应用程序
2. A Booking Application that allows a new Cargo to be registered and prepares the system for it
3.一个事件日志应用程序,可以记录货物的每次处理(提供跟踪查询找到的信息)
3. An Incident Logging Application that can record each handling of the Cargo (providing the information that is found by the Tracking Query)
这些应用类是协调者,它们不应该自己去解答它们提出的问题。那是领域层的工作。
These application classes are coordinators. They should not work out the answers to the questions they ask. That is the domain layer’s job.
我们将逐一分析每个对象,寻找必须追踪的身份标识或所代表的基本值。首先,我们将处理那些清晰明确的情况,然后再考虑那些较为模糊的情况。
Considering each object in turn, we’ll look for identity that must be tracked or a basic value that is represented. First we’ll go through the clear-cut cases and then consider the more ambiguous ones.
我们先从一个简单的例子开始。客户对象代表个人或公司,也就是通常意义上的实体。客户对象显然具有对用户重要的身份信息,因此在模型中它是一个实体。那么如何追踪客户信息呢?在某些情况下,税务识别号或许可行,但对于跨国公司而言,税务识别号可能并不适用。这个问题需要咨询领域专家。我们与一家航运公司的业务人员讨论了这个问题,发现该公司已经有一个客户数据库,每个客户在首次销售接触时都会被分配一个ID号码。这个ID号码已经在公司内部使用;在我们的软件中使用该号码可以确保不同系统之间身份信息的一致性。最初,这个ID号码需要手动输入。
Let’s start with an easy one. A Customer object represents a person or a company, an entity in the usual sense of the word. The Customer object clearly has identity that matters to the user, so it is an ENTITY in the model. How to track it? Tax ID might be appropriate in some cases, but an international company could not use that. This question calls for consultation with a domain expert. We discuss the problem with a businessperson in the shipping company, and we discover that the company already has a customer database in which each Customer is assigned an ID number at first sales contact. This ID is already used throughout the company; using the number in our software will establish continuity of identity between those systems. It will initially be a manual entry.
两个相同的货箱必须能够区分,因此货物对象是实体。实际上,所有货运公司都会为每件货物分配一个追踪ID。该ID将自动生成,用户可见,并且在本例中,很可能在预订时告知客户。
Two identical crates must be distinguishable, so Cargo objects are ENTITIES. In practice, all shipping companies assign tracking IDs to each piece of cargo. This ID will be automatically generated, visible to the user, and in this case, probably conveyed to the customer at booking time.
我们关注此类个别事件,因为它们使我们能够追踪事态发展。它们反映了现实世界的事件,而这些事件通常不可互换,因此它们是独立的实体。每次承运人运输都会通过从货运计划中获取的代码进行识别。
We care about such individual incidents because they allow us to keep track of what is going on. They reflect real-world events, which are not usually interchangeable, so they are ENTITIES. Each Carrier Movement will be identified by a code obtained from a shipping schedule.
与领域专家的另一次讨论表明,可以通过货物ID的组合来唯一识别处理事件;完成时间和类型。例如,同一货物不能同时进行装载和卸载。
Another discussion with a domain expert reveals that Handling Events can be uniquely identified by the combination of Cargo ID, completion time, and type. For example, the same Cargo cannot be both loaded and unloaded at the same time.
两个同名地点并非同一地点。经纬度可以提供一个唯一的标识符,但这可能不太实用,因为这些测量值与该系统的大多数用途无关,而且计算起来也相当复杂。更可能的情况是,位置信息将成为某种地理模型的一部分,该模型会根据航道和其他特定领域的因素将地点关联起来。因此,一个任意的、内部自动生成的标识符就足够了。
Two places with the same name are not the same. Latitude and longitude could provide a unique key, but probably not a very practical one, since those measurements are not of interest to most purposes of this system, and they would be fairly complicated. More likely, the Location will be part of a geographical model of some kind that will relate places according to shipping lanes and other domain-specific concerns. So an arbitrary, internal, automatically generated identifier will suffice.
这有点棘手。交付历史记录不可互换,因此它们是实体。但交付历史记录与其对应的货物之间存在一对一的关系,所以它本身并没有真正的身份。它的身份是从拥有它的货物那里借用的。当我们对聚合进行建模时,这一点会更加清晰。
This is a tricky one. Delivery Histories are not interchangeable, so they are ENTITIES. But a Delivery History has a one-to-one relationship with its Cargo, so it doesn’t really have an identity of its own. Its identity is borrowed from the Cargo that owns it. This will become clearer when we model the AGGREGATES.
虽然它代表了货物的最终目标,但这种抽象并不依赖于货物本身。它实际上表达的是某种交付历史的假设状态。我们希望附加到货物的交付历史最终能够满足附加到货物的交付规范。如果有两批货物运往同一地点,它们可以共享相同的交付规范,但不能共享相同的交付历史,即使它们的交付历史初始状态相同(均为空)。交付规范是值对象。
Although it represents the goal of a Cargo, this abstraction does not depend on Cargo. It really expresses a hypothetical state of some Delivery History. We hope that the Delivery History attached to our Cargo will eventually satisfy the Delivery Specification attached to our Cargo. If we had two Cargoes going to the same place, they could share the same Delivery Specification, but they could not share the same Delivery History, even though the histories start out the same (empty). Delivery Specifications are VALUE OBJECTS.
角色描述了它所关联的某些信息,但它没有历史记录或连续性。它是一个值对象,可以在不同的货物/客户关联之间共享。
Role says something about the association it qualifies, but it has no history or continuity. It is a VALUE OBJECT, and it could be shared among different Cargo/Customer associations.
其他属性(例如时间戳或名称)是值对象。
Other attributes such as time stamps or names are VALUE OBJECTS.
原始图中所有关联关系均未指定遍历方向,但双向关联在设计中往往存在问题。此外,遍历方向通常能够揭示领域知识,从而深化模型本身。
None of the associations in the original diagram specified a traversal direction, but bidirectional associations are problematic in a design. Also, traversal direction often captures insight into the domain, deepening the model itself.
如果客户能够直接关联到其发货的每一批货物,对于长期回头客来说将会非常不便。此外, “客户”的概念并非仅限于货物。在大型系统中,“客户”可能涉及多个对象。最好不要让“客户”承担如此具体的职责。如果我们需要按客户查找货物,可以通过数据库查询来实现。我们将在本章后面的“存储库”部分再次讨论这个问题。
If the Customer has a direct reference to every Cargo it has shipped, it will become cumbersome for long-term, repeat Customers. Also, the concept of a Customer is not specific to Cargo. In a large system, the Customer may have roles to play with many objects. Best to keep it free of such specific responsibilities. If we need the ability to find Cargoes by Customer, this can be done through a database query. We’ll return to this issue later in this chapter, in the section on REPOSITORIES.
如果我们的应用是跟踪船舶库存,那么从“承运人移动”到“装卸事件”的遍历就非常重要。但我们的业务只需要跟踪货物。因此,仅允许从“装卸事件”到“承运人移动”的关联关系就能体现我们对业务的理解。此外,由于不允许多重方向,这种做法也将实现简化为简单的对象引用。
If our application were tracking the inventory of ships, traversal from Carrier Movement to Handling Event would be important. But our business needs to track only the Cargo. Making the association traversable only from Handling Event to Carrier Movement captures that understanding of our business. This also reduces the implementation to a simple object reference, because the direction with multiplicity was disallowed.
其余决策背后的理由在下一页的图 7.2中进行了解释。
The rationale behind the remaining decisions is explained in Figure 7.2, on the next page.
图 7.2. 某些关联的遍历方向受到限制。
Figure 7.2. Traversal direction has been constrained on some associations.
我们的模型中存在一个循环引用:货物(Cargo)知道它的交付历史记录(Delivery History),而交付历史记录包含一系列处理事件(Handling Events),这些事件又指向货物本身。循环引用在很多领域都存在,有时在设计中也是必要的,但它们的维护却很棘手。通过避免在两个需要保持同步的地方保存相同的信息,我们可以优化实现方式。在这种情况下,我们可以在初始原型中用 Java 实现一个简单但脆弱的方案,即给交付历史记录添加一个包含处理事件的List对象。但最终我们可能需要放弃这个集合,转而使用以货物为键的数据库查找。在选择存储库( REPOSITORIES)时,我们会再次讨论这个问题。如果查询历史记录的频率相对较低,那么这种方式应该能够带来良好的性能,简化维护,并减少添加处理事件的开销。如果查询非常频繁,那么最好还是直接维护指向货物的指针。这些设计上的权衡体现在实现的简易性与维护的复杂性之间。性能。模型相同;它包含循环和双向关联。
There is one circular reference in our model: Cargo knows its Delivery History, which holds a series of Handling Events, which in turn point back to the Cargo. Circular references logically exist in many domains and are sometimes necessary in design as well, but they are tricky to maintain. Implementation choices can help by avoiding holding the same information in two places that must be kept synchronized. In this case, we can make a simple but fragile implementation (in Java) in an initial prototype, by giving Delivery History a List object containing Handling Events. But at some point we’ll probably want to drop the collection in favor of a database lookup with Cargo as the key. This discussion will be taken up again when choosing REPOSITORIES. If the query to see the history is relatively infrequent, this should give good performance, simplify maintenance, and reduce the overhead of adding Handling Events. If this query is very frequent, then it is better to go ahead and maintain the direct pointer. These design trade-offs balance simplicity of implementation against performance. The model is the same; it contains the cycle and the bidirectional association.
客户、地点和承运商运输各自具有独立的身份,并且被多个货物共享,因此它们必然是各自聚合的根节点,这些聚合包含它们的属性,以及可能包含的、超出本文讨论范围的其他对象。货物显然也是一个聚合根节点,但如何界定其边界则需要仔细考虑。
Customer, Location, and Carrier Movement have their own identities and are shared by many Cargoes, so they must be the roots of their own AGGREGATES, which contain their attributes and possibly other objects below the level of detail of this discussion. Cargo is also an obvious AGGREGATE root, but where to draw the boundary takes some thought.
货物聚合 表可以囊括除特定货物本身之外不存在的所有信息,包括交付历史记录、交付规范和处理事件。这很符合交付历史记录的用途。没有人会直接查找交付历史记录,除非他们想要查找货物本身。因此,无需直接查找。由于交付历史记录具有全局访问权限,并且其身份实际上只是从货物中派生而来,因此它能够很好地融入货物的边界内,并且无需作为根节点。交付规范是一个值对象,因此将其包含在货物聚合中不会产生任何问题。
The Cargo AGGREGATE could sweep in everything that would not exist but for the particular Cargo, which would include the Delivery History, the Delivery Specification, and the Handling Events. This fits for Delivery History. No one would look up a Delivery History directly without wanting the Cargo itself. With no need for direct global access, and with an identity that is really just derived from the Cargo, the Delivery History fits nicely inside Cargo’s boundary, and it does not need to be a root. The Delivery Specification is a VALUE OBJECT, so there are no complications from including it in the Cargo AGGREGATE.
处理事件则另当别论。之前我们考虑过两种可能的数据库查询来查找这些事件:一种是查找交付历史记录的处理事件,作为集合的替代方案,该查询将局限于货物聚合(Cargo AGGREGATE)内部;另一种是查找为特定承运商运输装载和准备的所有操作。在第二种情况下,即使将货物处理活动与货物本身分开考虑,它似乎也具有一定的意义。因此,处理事件应该作为其自身聚合的根。
The Handling Event is another matter. Previously we have considered two possible database queries that would search for these: one, to find the Handling Events for a Delivery History as a possible alternative to the collection, would be local within the Cargo AGGREGATE; the other would be used to find all the operations to load and prepare for a particular Carrier Movement. In the second case, it seems that the activity of handling the Cargo has some meaning even when considered apart from the Cargo itself. So the Handling Event should be the root of its own AGGREGATE.
图 7.3.应用于模型的聚合边界。(注:位于绘制边界之外的实体被视为其自身聚合的根。)
Figure 7.3. AGGREGATE boundaries imposed on the model. (Note: An ENTITY outside a drawn boundary is implied to be the root of its own AGGREGATE.)
共有五个实体 在设计中,它们是聚合的根,因此我们可以将考虑范围限制在这些根上,因为其他对象都不允许有存储库。
There are five ENTITIES in the design that are roots of AGGREGATES, so we can limit our consideration to these, since none of the other objects is allowed to have REPOSITORIES.
为了确定哪些候选对象应该拥有自己的存储库,我们必须回顾一下应用程序的需求。用户要通过预订应用程序进行预订,需要选择扮演不同角色的客户(例如发货人、收货人等)。因此,我们需要一个客户存储库。此外,我们还需要找到一个位置来指定货物的目的地,所以我们创建一个位置存储库。
To decide which of these candidates should actually have a REPOSITORY, we must go back to the application requirements. In order to take a booking through the Booking Application, the user needs to select the Customer(s) playing the various roles (shipper, receiver, and so on). So we need a Customer Repository. We also need to find a Location to specify as the destination for the Cargo, so we create a Location Repository.
活动日志应用程序需要允许用户查找货物装载到的承运商运输信息,因此我们需要一个承运商运输信息库。该用户还必须告知系统已装载的货物,因此我们需要一个货物信息库。
The Activity Logging Application needs to allow the user to look up the Carrier Movement that a Cargo is being loaded onto, so we need a Carrier Movement Repository. This user must also tell the system which Cargo has been loaded, so we need a Cargo Repository.
图 7.4. R存储库提供对选定聚合根的访问。
Figure 7.4. REPOSITORIES give access to selected AGGREGATE roots.
目前还没有事件处理存储库,因为我们在第一版中决定将与交付历史记录的关联实现为一个集合,而且我们的应用程序目前没有需求来了解承运商运输单上装载了哪些货物。这两个原因都可能发生变化;如果发生变化,我们将添加一个存储库。
For now there is no Handling Event Repository, because we decided to implement the association with Delivery History as a collection in the first iteration, and we have no application requirement to find out what has been loaded onto a Carrier Movement. Either of these reasons could change; if they did, then we would add a REPOSITORY.
为了核实所有这些决定,我们必须不断演练各种场景,以确认我们能够有效地解决应用问题。
To cross-check all these decisions, we have to constantly step through scenarios to confirm that we can solve application problems effectively.
偶尔会有客户打电话来说:“糟了!我们明明说好把货物送到哈肯萨克,但现在我们真的需要送到霍博肯。” 我们竭诚为您服务,所以系统必须能够应对这种变化。
Occasionally a Customer calls up and says, “Oh no! We said to send our cargo to Hackensack, but we really need it in Hoboken.” We are here to serve, so the system is required to provide for this change.
交付规范是一个值对象,因此最简单的办法就是将其丢弃并获取一个新的,然后使用货物上的 setter 方法将旧的替换为新的。
Delivery Specification is a VALUE OBJECT, so it would be simplest to just to throw it away and get a new one, then use a setter method on Cargo to replace the old one with the new one.
用户表示,同一客户的重复预订往往比较相似,因此他们希望将旧货物作为新货物的原型。该应用程序将允许他们在存储库中查找货物,然后选择一个命令,基于所选货物创建新货物。我们将使用原型模式(Gamma 等人,1995)来设计此功能。
The users say that repeated bookings from the same Customers tend to be similar, so they want to use old Cargoes as prototypes for new ones. The application will allow them to find a Cargo in the REPOSITORY and then select a command to create a new Cargo based on the selected one. We’ll design this using the PROTOTYPE pattern (Gamma et al. 1995).
Cargo是一个实体,也是一个聚合的根。因此,必须谨慎复制它;我们需要考虑其聚合边界内的每个对象或属性应该如何处理。让我们逐一分析:
Cargo is an ENTITY and is the root of an AGGREGATE. Therefore, it must be copied carefully; we need to consider what should happen to each object or attribute enclosed by its AGGREGATE boundary. Let’s go over each one:
•交付历史记录:我们应该创建一个新的空记录,因为旧记录的历史记录不再适用。这通常是聚合边界内的实体所面临的情况。
• Delivery History: We should create a new, empty one, because the history of the old one doesn’t apply. This is the usual case with ENTITIES inside the AGGREGATE boundary.
•客户角色:我们应该复制包含客户键值引用的映射(或其他集合) ,包括键值,因为它们在新发货中很可能扮演相同的角色。但我们必须注意不要复制客户本身。 对象本身。我们最终必须引用与之前引用的货物对象相同的客户对象,因为它们是聚合边界之外的实体。
• Customer Roles: We should copy the Map (or other collection) that holds the keyed references to Customers, including the keys, because they are likely to play the same roles in the new shipment. But we have to be careful not to copy the Customer objects themselves. We must end up with references to the same Customer objects as the old Cargo object referenced, because they are ENTITIES outside the AGGREGATE boundary.
•跟踪 ID:我们必须提供与创建新货物时相同的来源的新跟踪 ID。
• Tracking ID: We must provide a new Tracking ID from the same source as we would when creating a new Cargo from scratch.
请注意,我们复制了货物 聚合边界内的所有内容,我们对副本进行了一些修改,但我们完全没有影响聚合边界之外的任何内容。
Notice that we have copied everything inside the Cargo AGGREGATE boundary, we have made some modifications to the copy, but we have affected nothing outside the AGGREGATE boundary at all.
即使我们为货物创建了一个复杂的工厂,或者像在“重复业务”场景中那样使用另一个货物作为工厂,我们仍然需要一个原始构造函数。我们希望构造函数能够生成一个满足其不变式的对象,或者至少,对于实体而言,能够保持其原有的身份。
Even if we have a fancy FACTORY for Cargo, or use another Cargo as the FACTORY, as in the “Repeat Business” scenario, we still have to have a primitive constructor. We would like the constructor to produce an object that fulfills its invariants or at least, in the case of an ENTITY, has its identity intact.
基于这些决策,我们可以在Cargo中创建一个类似这样的FACTORY方法:
Given these decisions, we might create a FACTORY method on Cargo such as this:
public Cargo copyPrototype(String newTrackingID)
或者我们可以创建一个独立的工厂方法,例如:
Or we might make a method on a standalone FACTORY such as this:
public Cargo newCargo(Cargo prototype, String newTrackingID)
一个独立的FACTORY还可以封装获取新货物的(自动生成的)ID 的过程,在这种情况下,它只需要一个参数:
A standalone FACTORY could also encapsulate the process of obtaining a new (automatically generated) ID for a new Cargo, in which case it would need only one argument:
public Cargo newCargo(Cargo prototype)
从这些工厂返回的结果都是一样的:货物的交付历史记录为空,交付规范为空。
The result returned from any of these FACTORIES would be the same: a Cargo with an empty Delivery History, and a null Delivery Specification.
货物和交付历史记录之间存在双向关联,这意味着货物和交付历史记录都离不开对立面的指向,因此它们必须同时创建。请记住,货物是包含交付历史记录的聚合表的根。因此,我们可以允许Cargo 的构造函数或工厂创建交付历史记录。交付历史记录构造函数将接受一个Cargo 对象作为参数。结果如下所示:
The two-way association between Cargo and Delivery History means that neither Cargo nor Delivery History is complete without pointing to its counterpart, so they must be created together. Remember that Cargo is the root of the AGGREGATE that includes Delivery History. Therefore, we can allow Cargo’s constructor or FACTORY to create a Delivery History. The Delivery History constructor will take a Cargo as an argument. The result would be something like this:
public Cargo(String id) {
trackingID = id;
deliveryHistory = new DeliveryHistory(this);
customerRoles = new HashMap();
}
最终会生成一个新的货物,并附带一个新的交付历史记录,该历史记录指向该货物。交付历史记录构造函数仅供其聚合根(即货物)使用,从而封装了货物的组合。
The result is a new Cargo with a new Delivery History that points back to the Cargo. The Delivery History constructor is used exclusively by its AGGREGATE root, namely Cargo, so that the composition of Cargo is encapsulated.
每次在现实世界中处理货物时,都会有用户使用事件日志应用程序输入处理事件。
Each time the cargo is handled in the real world, some user will enter a Handling Event using the Incident Logging Application.
每个类都必须有原始构造函数。由于处理事件是一个实体,因此所有定义其标识的属性都必须传递给构造函数。如前所述,处理事件由其货物ID、完成时间和事件类型的组合唯一标识。处理事件的另一个属性是与承运人移动的关联,但某些类型的处理事件甚至没有此关联。一个用于创建有效处理事件的基本构造函数如下:
Every class must have primitive constructors. Because the Handling Event is an ENTITY, all attributes that define its identity must be passed to the constructor. As discussed previously, the Handling Event is uniquely identified by the combination of the ID of its Cargo, the completion time, and the event type. The only other attribute of Handling Event is the association to a Carrier Movement, which some types of Handling Events don’t even have. A basic constructor that creates a valid Handling Event would be:
public HandlingEvent(Cargo c, String eventType, Date timeStamp) {
handled = c;
type = eventType;
completionTime = timeStamp;
}
实体的非标识属性通常可以稍后添加。在本例中,处理事件的所有属性都将在初始事务中设置,并且永远不会更改(除非是为了更正数据输入错误),因此,为每种事件类型向处理事件添加一个简单的工厂方法,并接受所有必要的参数,会很方便,也能使客户端代码更具表达力。例如,“装载事件”确实涉及承运商移动:
Nonidentifying attributes of an ENTITY can usually be added later. In this case, all attributes of the Handling Event are going to be set in the initial transaction and never altered (except possibly for correcting a data-entry error), so it could be convenient, and make client code more expressive, to add a simple FACTORY METHOD to Handling Event for each event type, taking all the necessary arguments. For example, a “loading event” does involve a Carrier Movement:
public static HandlingEvent newLoading(
Cargo c, CarrierMovement loadedOnto, Date timeStamp) {
HandlingEvent result =
new HandlingEvent(c, LOADING_EVENT, timeStamp);
result.setCarrierMovement(loadedOnto);
return result;
}
处理事件 该模型中包含一个抽象层,它可能封装了各种专门的事件处理类,涵盖从装卸到密封、存储以及其他与载体无关的活动。这些事件处理类可能以多个子类的形式实现,或者具有复杂的初始化过程——或者两者兼而有之。通过为每种类型在基类(事件处理类)中添加工厂方法,实例创建过程被抽象化,使客户端无需了解具体的实现细节。工厂负责确定要实例化的类以及如何初始化它。
The Handling Event in the model is an abstraction that might encapsulate a variety of specialized Handling Event classes, ranging from loading and unloading to sealing, storing, and other activities not related to Carriers. They might be implemented as multiple subclasses or have complicated initialization—or both. By adding FACTORY METHODS to the base class (Handling Event) for each type, instance creation is abstracted, freeing the client from knowledge of the implementation. The FACTORY is responsible for knowing what class was to be instantiated and how it should be initialized.
遗憾的是,事情并非如此简单。从货物到交付历史记录再到历史事件,最后又回到货物的引用循环,使得实例创建变得复杂。交付历史记录保存着与其货物相关的处理事件集合,新对象必须作为事务的一部分添加到该集合中。如果未创建此反向指针,则对象将不一致。
Unfortunately, the story isn’t quite that simple. The cycle of references, from Cargo to Delivery History to History Event and back to Cargo, complicates instance creation. The Delivery History holds a collection of Handling Events relevant to its Cargo, and the new object must be added to this collection as part of the transaction. If this back-pointer were not created, the objects would be inconsistent.
图 7.5. 添加处理事件需要将其插入到交付历史记录中。
Figure 7.5. Adding a Handling Event requires inserting it into a Delivery History.
反向指针的创建可以封装在FACTORY中(并保留在它所属的领域层中),但现在我们将研究一种完全消除这种尴尬交互的替代设计。
Creation of the back-pointer could be encapsulated in the FACTORY (and kept in the domain layer where it belongs), but now we’ll look at an alternative design that eliminates this awkward interaction altogether.
建模和设计并非一个持续前进的过程。除非经常进行重构,利用新的见解来改进模型和设计,否则它将停滞不前。
Modeling and design is not a constant forward process. It will grind to a halt unless there is frequent refactoring to take advantage of new insights to improve the model and the design.
目前来看,这个设计虽然可行,也符合模型,但存在一些繁琐之处。设计之初看似无关紧要的问题,现在却开始变得令人烦恼。让我们回到其中一个问题,并借助事后诸葛亮的视角,对设计进行改进,使其更有利于我们。
By now, there are a couple of cumbersome aspects to this design, although it does work and it does reflect the model. Problems that didn’t seem important when starting the design are beginning to be annoying. Let’s go back to one of them and, with the benefit of hindsight, stack the design deck in our favor.
添加处理事件时,需要更新交付历史记录,这会导致货物聚合数据参与到事务中。如果其他用户同时正在修改货物,则处理事件事务可能会失败或延迟。输入处理事件是一项操作活动,需要快速简便,因此,能够无冲突地输入处理事件是应用程序的一项重要需求。这促使我们考虑采用不同的设计方案。
The need to update Delivery History when adding a Handling Event gets the Cargo AGGREGATE involved in the transaction. If some other user was modifying Cargo at the same time, the Handling Event transaction could fail or be delayed. Entering a Handling Event is an operational activity that needs to be quick and simple, so an important application requirement is the ability to enter Handling Events without contention. This pushes us to consider a different design.
将交付历史记录中的处理事件集合替换为查询,即可在不引发自身聚合之外任何完整性问题的情况下添加处理事件。此更改将使此类事务能够顺利完成,互不干扰。如果输入的处理事件数量众多而查询相对较少,则此设计效率更高。事实上,如果底层技术是关系数据库,则底层可能已经使用了查询来模拟集合。使用查询而非集合还可以降低维护货物和处理事件之间循环引用一致性的难度。
Replacing the Delivery History’s collection of Handling Events with a query would allow Handling Events to be added without raising any integrity issues outside its own AGGREGATE. This change would enable such transactions to complete without interference. If there are a lot of Handling Events being entered and relatively few queries, this design is more efficient. In fact, if a relational database is the underlying technology, a query was probably being used under the covers anyway to emulate the collection. Using a query rather than a collection would also reduce the difficulty of maintaining consistency in the cyclical reference between Cargo and Handling Event.
为了负责处理查询,我们将添加一个事件处理存储库。该事件处理存储库将支持对与特定货物相关的事件进行查询。此外,该存储库还可以提供针对特定问题进行优化的查询,从而高效地回答这些问题。例如,如果“交付历史记录”是常用的访问路径,则可以提供针对特定问题的优化查询。 为了查找上次报告的装卸货信息,从而推断货物的当前状态,我们可以设计一个查询,仅返回相关的装卸事件。如果我们想要查找特定承运商运输中装载的所有货物,也可以轻松添加查询。
To take responsibility for the queries, we’ll add a REPOSITORY for Handling Events. The Handling Event Repository will support a query for the Events related to a certain Cargo. In addition, the REPOSITORY can provide queries optimized to answer specific questions efficiently. For example, if a frequent access path is the Delivery History finding the last reported load or unload, in order to infer the current status of the Cargo, a query could be devised to return just that relevant Handling Event. And if we wanted a query to find all Cargoes loaded on a particular Carrier Movement, we could easily add it.
图 7.6. 将交付历史记录的处理事件集合实现为查询,使得插入处理事件变得简单,并且不会与货物聚合 发生冲突。
Figure 7.6. Implementing Delivery History’s collection of Handling Events as a query makes insertion of Handling Events simple and free of contention with the Cargo AGGREGATE.
这样一来,交付历史记录就没有持久状态了。目前,没有必要保留它。我们可以在需要回答问题时直接派生交付历史记录本身。之所以可以派生这个对象,是因为尽管实体会被反复创建,但与同一个货物对象的关联保证了不同版本之间的连续性。
This leaves the Delivery History with no persistent state. At this point, there is no real need to keep it around. We could derive Delivery History itself whenever it is needed to answer some question. We can derive this object because, although the ENTITY will be repeatedly recreated, the association with the same Cargo object maintains the thread of continuity between incarnations.
循环引用的创建和维护不再复杂。货物工厂将进行简化,不再为新实例附加空的交付历史记录。数据库空间可以略微减少,持久化对象的实际数量也可能减少。在某些对象数据库中,资源是有限的,而这在很大程度上会影响结果。如果用户通常只在货物到达之前查询其状态,那么很多不必要的工作就可以完全避免。
The circular reference is no longer tricky to create and maintain. The Cargo Factory will be simplified to no longer attach an empty Delivery History to new instances. Database space can be reduced slightly, and the actual number of persistent objects might be reduced considerably, which is a limited resource in some object databases. If the common usage pattern is that the user seldom queries for the status of a Cargo until it arrives, then a lot of unneeded work will be avoided altogether.
另一方面,如果我们使用的是对象数据库,遍历关联或显式集合可能比REPOSITORY查询快得多。如果访问模式是频繁列出完整历史记录,而不是偶尔查询上次位置,那么性能权衡可能更有利于显式集合。请记住,新增功能(“此承运商移动包含哪些内容?”)尚未被提出,而且可能永远不会被提出,因此我们不想为此选项支付太多费用。
On the other hand, if we are using an object database, traversing an association or an explicit collection is probably much faster than a REPOSITORY query. If the access pattern includes frequent listing of the full history, rather than the occasional targeted query of last position, the performance trade-off might favor the explicit collection. And remember that the added feature (“What is on this Carrier Movement?”) hasn’t been requested yet, and may never be, so we don’t want to pay much for that option.
这类替代方案和设计权衡比比皆是,即使在这个简化的系统中,我也能举出很多例子。但关键在于,这些都是同一模型内的自由度。通过对值、实体及其聚合进行建模,我们降低了此类设计变更的影响。例如,在本例中,所有变更都封装在货物的 聚合边界内。虽然还需要添加事件处理存储库,但无需对事件处理本身进行任何重新设计(尽管根据存储库框架的具体细节,可能需要进行一些实现上的更改)。
These kinds of alternatives and design trade-offs are everywhere, and I could come up with lots of examples just in this little simplified system. But the important point is that these are degrees of freedom within the same model. By modeling VALUES, ENTITIES, and their AGGREGATES as we have, we have reduced the impact of such design changes. For example, in this case all changes are encapsulated within the Cargo’s AGGREGATE boundary. It also required the addition of the Handling Event Repository, but it did not call for any redesign of the Handling Event itself (although some implementation changes might be involved, depending on the details of the REPOSITORY framework).
到目前为止,我们只考察了少数几个对象,所以模块化问题并不突出。现在,让我们来看一个运输模型中更大一些的部分(当然,仍然是简化的),看看它是如何组织成各个模块的,这些模块会影响整个模型。
So far we’ve been looking at so few objects that modularity is not an issue. Now let’s look at a little bigger part of a shipping model (though still simplified, of course) to see its organization into MODULES that will affect the model.
图 7.7展示了一个由本书一位假想的热情读者精心划分的模型。该图是第五章提出的基础设施驱动打包问题的变体。在这种情况下,对象根据各自遵循的模式进行分组。结果是,概念上关联性很弱的对象(低内聚性)被挤在一起,而所有模块之间的关联则杂乱无章(高耦合性)。这些包讲述了一个故事,但并非关于交付的故事,而是关于开发者当时正在阅读的内容的故事。
Figure 7.7 shows a model neatly partitioned by a hypothetical enthusiastic reader of this book. This diagram is a variation on the infrastructure-driven packaging problem raised in Chapter 5. In this case, the objects have been grouped according to the pattern each follows. The result is that objects that conceptually have little relationship (low cohesion) are crammed together, and associations run willy-nilly between all the MODULES (high coupling). The packages tell a story, but it is not the story of shipping; it is the story of what the developer was reading at the time.
图 7.7。这些模块不传递领域知识。
Figure 7.7. These MODULES do not convey domain knowledge.
按模式划分似乎是一个明显的错误,但它实际上并不比将持久对象与瞬态对象分开或任何其他不以对象含义为基础的方法论方案更不明智。
Partitioning by pattern may seem like an obvious error, but it is not really any less sensible than separating persistent objects from transient ones or any other methodical scheme that is not grounded in the meaning of the objects.
相反,我们应该寻找内在的连贯概念,并专注于我们想要向项目其他成员传达的信息。与小规模建模决策一样,有很多方法可以实现这一点。图 7.8展示了一种简单直接的方法。
Instead, we should be looking for the cohesive concepts and focusing on what we want to communicate to others on the project. As with smaller scale modeling decisions, there are many ways to do it. Figure 7.8 shows a straightforward one.
图 7.8.基于广泛领域概念的模块
Figure 7.8. MODULES based on broad domain concepts
图 7.8中的模块名称有助于团队的语言表达。我们公司负责客户的运输,以便向他们开具发票。我们的销售和市场人员负责与客户沟通并签订协议。运营人员负责运输,将货物运送到指定目的地。后台部门负责计费,根据客户协议中的价格提交发票。这就是我可以用这组模块讲述的故事之一。
The MODULE names in Figure 7.8 contribute to the team’s language. Our company does shipping for customers so that we can bill them. Our sales and marketing people deal with customers, and make agreements with them. The operations people do the shipping, getting the cargo to its specified destination. The back office takes care of billing, submitting invoices according to the pricing in the customer’s agreement. That’s one story I can tell with this set of MODULES.
这种直观的分解当然可以在后续的迭代中得到改进,甚至完全被替换,但它现在正在帮助模型驱动设计,并为普适语言做出贡献。
This intuitive breakdown could be refined, certainly, in successive iterations, or even replaced entirely, but it is now aiding MODEL-DRIVEN DESIGN and contributing to the UBIQUITOUS LANGUAGE.
到目前为止,我们一直都是根据最初的需求和模型开展工作。现在,我们将添加第一批主要的新功能。
Up to this point, we’ve been working off the initial requirements and model. Now the first major new functions are going to be added.
这家虚构的航运公司的销售部门使用其他软件来管理客户关系、销售预测等等。第四,其中一项功能支持收益管理,允许公司根据货物类型、始发地和目的地,或任何其他可作为类别名称输入的因素,来分配特定类型货物的预订量。这些因素构成了每种类型货物的销售目标,从而避免利润更高的业务被利润较低的货物挤占,同时避免预订不足(未能充分利用运力)或过度预订(导致频繁的货物调整,从而损害客户关系)。
The sales division of the imaginary shipping company uses other software to manage client relationships, sales projections, and so forth. One feature supports yield management by allowing the firm to allocate how much cargo of specific types they will attempt to book based on the type of goods, the origin and destination, or any other factor they may choose that can be entered as a category name. These constitute goals of how much will be sold of each type, so that more profitable types of business will not be crowded out by less profitable cargoes, while at the same time avoiding underbooking (not fully utilizing their shipping capacity) or excessive overbooking (resulting in bumping cargo so often that it hurts customer relationships).
现在他们希望将这项功能集成到预订系统中。当收到预订信息时,他们希望系统能够将其与这些配额进行比对,以确定是否接受该预订。
Now they want this feature to be integrated with the booking system. When a booking comes in, they want it checked against these allocations to see if it should be accepted.
所需信息存储在两个地方,预订应用程序需要查询这两个地方,以便接受或拒绝预订请求。信息流的大致流程图如下所示。
The information needed resides in two places, which will have to be queried by the Booking Application so that it can either accept or reject the requested booking. A sketch of the general information flows looks something like this.
图 7.9. 我们的预订应用程序必须使用来自销售管理系统和我们自己的域存储库的信息。
Figure 7.9. Our Booking Application must use information from the Sales Management System and from our own domain REPOSITORIES.
销售管理系统的设计初衷与我们这里使用的模型并不相同。如果预订应用程序直接与其交互,我们的应用程序就必须适应另一个系统的设计,这将使保持清晰的模型驱动设计变得更加困难,并会扰乱通用语言(UBIQUITOUS LANGUAGE) 。因此,我们创建一个新的类,其职责是在我们的模型和销售管理系统的语言之间进行转换。它并非一个通用的转换机制,而是只公开我们应用程序所需的功能,并根据我们的领域模型对其进行重新抽象。这个类将充当防腐层(详见第14章)。
The Sales Management System was not written with the same model in mind that we are working with here. If the Booking Application interacts with it directly, our application will have to accommodate the other system’s design, which will make it harder to keep a clear MODEL-DRIVEN DESIGN and will confuse the UBIQUITOUS LANGUAGE. Instead, let’s create another class whose job it will be to translate between our model and the language of the Sales Management System. It will not be a general translation mechanism. It will expose just the features our application needs, and it will reabstract them in terms of our domain model. This class will act as an ANTICORRUPTION LAYER (discussed in Chapter 14).
这是一个与销售管理系统的接口,所以我们最初可能会想把它命名为“销售管理接口”之类的名称。但这样我们就错失了一个机会,可以利用更适合我们的方式来重新表述问题。因此,让我们为需要从另一个系统中获取的每个分配函数定义一个服务(SERVICE) 。我们将使用一个类来实现这些服务,该类的名称反映了它在我们系统中的职责:“分配检查器”。
This is an interface to the Sales Management System, so we might first think of calling it something like “Sales Management Interface.” But we would be missing an opportunity to use language to recast the problem along lines more useful to us. Instead, let’s define a SERVICE for each of the allocation functions we need to get from the other system. We’ll implement the SERVICES with a class whose name reflects its responsibility in our system: “Allocation Checker.”
如果需要进行其他集成(例如,使用销售管理系统的客户数据库而不是我们自己的客户 存储库),可以创建一个新的转换器,并由SERVICES类来承担该职责。虽然保留一个类似“销售管理系统接口”的底层类来处理与其他程序的通信机制仍然有用,但它无需负责转换工作。此外,它会被“分配检查器”隐藏,因此不会出现在领域设计中。
If some other integration is needed (for example, using the Sales Management System’s customer database instead of our own Customer REPOSITORY), another translator can be created with SERVICES fulfilling that responsibility. It might still be useful to have a lower level class like Sales Management System Interface to handle the machinery of talking to the other program, but it wouldn’t be responsible for translation. Also, it would be hidden behind the Allocation Checker, so it wouldn’t show up in the domain design.
既然我们已经概述了两个系统的交互方式,那么我们将提供什么样的接口来回答“这种类型的货物可以预订多少?”这个问题呢?棘手的问题在于如何定义货物的“类型” ,因为我们的领域模型目前还没有对货物进行分类。在销售管理系统中,货物类型只是一组类别关键字,我们可以将我们的类型与该列表保持一致。我们可以传入一个字符串集合作为参数。但这会让我们错失另一个机会:重新抽象另一个系统的领域。我们需要丰富我们的领域模型,使其能够容纳货物存在类别这一知识。我们应该与领域专家集思广益,共同制定新的概念。
Now that we have outlined the interaction of the two systems, what kind of interface are we going to supply that can answer the question “How much of this type of Cargo may be booked?” The tricky issue is to define what the “type” of a Cargo is, because our domain model does not categorize Cargoes yet. In the Sales Management System, Cargo types are just a set of category keywords, and we could conform our types to that list. We could pass in a collection of strings as an argument. But we would be passing up another opportunity: this time, to reabstract the domain of the other system. We need to enrich our domain model to accommodate the knowledge that there are categories of cargo. We should brainstorm with a domain expert to work out the new concept.
有时(正如第 11 章将要讨论的),分析模式可以为我们提供建模解决方案的思路。《分析模式》 (Fowler,1996)一书中描述了一种解决此类问题的模式:企业细分。企业细分是一组维度,用于定义业务的分解方式。这些维度可以包括前面提到的所有航运业务维度,以及时间维度,例如本月至今。在我们的分配模型中使用这个概念,可以使模型更具表达力,并简化接口。一个名为“企业细分”的类 将会在我们的领域模型和设计中作为额外的VALUE OBJECT出现,需要为每个货物派生该 VALUE OBJECT 。
Sometimes (as will be discussed in Chapter 11) an analysis pattern can give us an idea for a modeling solution. The book Analysis Patterns (Fowler 1996) describes a pattern that addresses this kind of problem: the ENTERPRISE SEGMENT. An ENTERPRISE SEGMENT is a set of dimensions that define a way of breaking down a business. These dimensions could include all those mentioned already for the shipping business, as well as time dimensions, such as month to date. Using this concept in our model of allocation makes the model more expressive and simplifies the interfaces. A class called “Enterprise Segment” will appear in our domain model and design as an additional VALUE OBJECT, which will have to be derived for each Cargo.
图 7.10。分配检查器充当反腐败层,根据我们的领域模型,向销售管理系统提供选择性接口。
Figure 7.10. The Allocation Checker acts as an ANTICORRUPTION LAYER presenting a selective interface to the Sales Management System in terms of our domain model.
分配检查器会在企业段和外部系统的类别名称之间进行转换。货物存储库也必须提供基于企业段的查询。在这两种情况下,都可以通过与企业段对象协作来执行操作,而不会破坏段的封装性,也不会使自身的实现变得复杂。(请注意,货物存储库返回的是计数,而不是实例集合。)
The Allocation Checker will translate between Enterprise Segments and the category names of the external system. The Cargo Repository must also provide a query based on the Enterprise Segment. In both cases, collaboration with the Enterprise Segment object can be used to perform the operations without breaching the Segment’s encapsulation and complicating their own implementations. (Notice that the Cargo Repository is answering a query with a count, rather than a collection of instances.)
这个设计仍然存在一些问题。
There are still a few problems with this design.
1.我们已赋予订舱应用程序执行以下规则的任务:“如果分配给货物所属企业舱位的空间大于已预订数量加上新货物的尺寸,则接受该货物。” 执行业务规则是领域职责,不应在应用层执行。
1. We have given the Booking Application the job of applying this rule: “A Cargo is accepted if the space allocated for its Enterprise Segment is greater than the quantity already booked plus the size of the new Cargo.” Enforcing a business rule is domain responsibility and shouldn’t be performed in the application layer.
2.预订应用程序如何得出企业细分尚不清楚。
2. It isn’t clear how the Booking Application derives the Enterprise Segment.
这两项职责似乎都应由分配检查器承担。更改其接口可以将这两个服务分开,使交互更加清晰明确。
Both of these responsibilities seem to belong to the Allocation Checker. Changing its interface can separate these two SERVICES and make the interaction clear and explicit.
图 7.11. 领域职责从预订应用程序转移到分配检查器
Figure 7.11. Domain responsibilities shifted from Booking Application to Allocation Checker
此集成带来的唯一严重限制是,销售管理系统不得使用分配检查器无法转换为企业段的维度。(如果不应用企业段模式,同样的限制将迫使销售系统仅使用可用于查询货物存储库的维度。这种方法虽然可行,但销售系统会扩展到域的其他部分。在此设计中,货物存储库只需设计为处理企业段,销售系统的更改只会影响到分配检查器,而分配检查器最初就是作为外观模式设计的。)
The only serious constraint imposed by this integration will be that the Sales Management System mustn’t use dimensions that the Allocation Checker can’t turn into Enterprise Segments. (Without applying the ENTERPRISE SEGMENT pattern, the same constraint would force the sales system to use only dimensions that can be used in a query to the Cargo Repository. This approach is feasible, but the sales system spills into other parts of the domain. In this design, the Cargo Repository need only be designed to handle Enterprise Segment, and changes in the sales system ripple only as far as the Allocation Checker, which was conceived as a FACADE in the first place.)
尽管分配检查器的接口是唯一与域设计其他部分相关的部分,但其内部实现可以为解决可能出现的性能问题提供机会。例如,如果销售管理系统运行在另一台服务器上(可能位于其他位置),通信开销可能会很大,并且每次分配检查都需要两次消息交换。第二个消息是必须的,它调用销售管理系统来回答是否应该接受特定货物的基本问题。但是,第一个消息(用于推导货物的企业段)所基于的数据和行为相对静态,与分配决策本身相比则不然。一种设计方案是缓存。这样一来,就可以将这些信息重新部署到服务器上的分配检查器中,从而将消息传递开销减少一半。这种灵活性是有代价的。设计更加复杂,而且重复的数据现在必须以某种方式保持最新。但是,当分布式系统的性能至关重要时,灵活的部署就可能成为一个重要的设计目标。
Although the Allocation Checker’s interface is the only part that concerns the rest of the domain design, its internal implementation can present opportunities to solve performance problems, if they arise. For example, if the Sales Management System is running on another server, perhaps at another location, the communications overhead could be significant, and there are two message exchanges for each allocation check. There is no alternative to the second message, which invokes the Sales Management System to answer the basic question of whether a certain cargo should be accepted. But the first message, which derives the Enterprise Segment for a cargo, is based on relatively static data and behavior compared to the allocation decisions themselves. One design option would be to cache this information so that it could be relocated on the server with the Allocation Checker, reducing messaging overhead by half. There is a price for this flexibility. The design is more complicated and the duplicated data must now be kept up to date somehow. But when performance is critical in a distributed system, flexible deployment can be an important design goal.
就是这样。这种整合原本可能会把我们简单、概念一致的设计变成一团乱麻,但现在,通过使用反腐败层、服务和一些企业级细分,我们已经将销售管理系统的功能干净利落地集成到我们的预订系统中,丰富了该领域。
That’s it. This integration could have turned our simple, conceptually consistent design into a tangled mess, but now, using an ANTICORRUPTION LAYER, a SERVICE, and some ENTERPRISE SEGMENTS, we have integrated the functionality of the Sales Management System into our booking system cleanly, enriching the domain.
最后一个设计问题:为什么不让Cargo负责派生企业细分?乍一看,如果派生所依据的所有数据都在Cargo中,那么将其作为Cargo的派生属性似乎很优雅。然而,事情并非如此简单。企业细分是根据业务战略的需要随意划分的。同一个实体可能出于不同的目的而被划分成不同的细分。例如,我们为了订舱分配的目的而为某个特定的Cargo派生了细分,但出于税务会计的目的,它可能具有完全不同的企业细分。即使是分配的企业细分,如果由于新的销售策略而重新配置销售管理系统,也可能会发生变化。因此, Cargo必须了解分配检查器,这远远超出了它的概念职责范围,而且它还将承担大量用于派生特定类型企业细分的方法。因此,派生此值的责任应该由了解细分规则的对象承担,而不是由拥有适用这些规则的数据的对象承担。这些规则可以拆分成一个单独的“策略”对象,并将其传递给货物(Cargo)以使其能够派生企业段(Enterprise Segment)。该方案似乎超出了我们目前的需求,但它可以作为后续设计的备选方案,而且应该不会造成太大的干扰。
A final design question: Why not give Cargo the responsibility of deriving the Enterprise Segment? At first glance it seems elegant, if all the data the derivation is based on is in the Cargo, to make it a derived attribute of Cargo. Unfortunately, it is not that simple. Enterprise Segments are defined arbitrarily to divide along lines useful for business strategy. The same ENTITIES could be segmented differently for different purposes. We are deriving the segment for a particular Cargo for booking allocation purposes, but it could have a completely different Enterprise Segment for tax accounting purposes. Even the allocation Enterprise Segment could change if the Sales Management System is reconfigured because of a new sales strategy. So the Cargo would have to know about the Allocation Checker, which is well outside its conceptual responsibility, and it would be laden with methods for deriving specific types of Enterprise Segment. Therefore, the responsibility for deriving this value lies properly with the object that knows the rules for segmentation, rather than the object that has the data to which those rules apply. Those rules could be split out into a separate “Strategy” object, which could be passed to a Cargo to allow it to derive an Enterprise Segment. That solution seems to go beyond the requirements we have here, but it would be an option for a later design and shouldn’t be a very disruptive change.
本书第二部分为保持模型与实现之间的一致性奠定了基础。使用一套经过验证的基本构建模块以及一致的语言,能够使开发工作更加合理有序。
Part II of this book laid a foundation for maintaining the correspondence between model and implementation. Using a proven set of basic building blocks along with consistent language brings some sanity to the development effort.
当然,真正的挑战在于找到一个精准的模型,它既能捕捉领域专家的细微关注点,又能指导实际的设计。最终,我们希望开发出一个能够深入理解领域的模型。这将使软件更贴合领域专家的思维方式,更能响应用户的需求。本书的这一部分将阐明这一目标,描述实现目标的流程,并解释一些设计原则和模式,以使设计既能满足应用程序的需求,又能满足开发人员自身的需求。
Of course, the real challenge is to actually find an incisive model, one that captures subtle concerns of the domain experts and can drive a practical design. Ultimately, we hope to develop a model that captures a deep understanding of the domain. This should make the software more in tune with the way the domain experts think and more responsive to the user’s needs. This part of the book will clarify that goal, describe the process by which it can be approached, and explain some design principles and patterns to apply to make the design accommodate the needs of the application as well as the developers themselves.
成功开发实用模型的关键在于三点。
Success developing useful models comes down to three points.
1.构建复杂的领域模型是可行的,而且值得付出努力。
1. Sophisticated domain models are achievable and worth the trouble.
2.它们很少是通过迭代重构过程开发的,包括领域专家与有兴趣了解该领域的开发人员的密切参与。
2. They are seldom developed except through an iterative process of refactoring, including close involvement of the domain experts with developers interested in learning about the domain.
3.它们可能需要复杂的设计技能才能实施和有效使用。
3. They may call for sophisticated design skills to implement and to use effectively.
重构是指在不改变软件功能的前提下对其进行重新设计。开发者无需预先做出复杂的设计决策,而是通过一系列持续的、细微的、离散的设计变更来改进代码,每次变更都保持现有功能不变,同时使设计更加灵活或易于理解。一套自动化单元测试使得开发者可以相对安全地进行代码实验。这一过程使开发者无需考虑长远发展。
Refactoring is the redesign of software in ways that do not change its functionality. Rather than making elaborate up-front design decisions, developers take code through a continuous series of small, discrete design changes, each leaving existing functionality unchanged while making the design more flexible or easier to understand. A suite of automated unit tests allows relatively safe experimentation with the code. The process frees the developers from the need to look far ahead.
但几乎所有关于如何重构的文献都侧重于对代码进行机械性的修改,使其更易于阅读或在非常细致的层面上进行改进。“按模式重构”的方法¹ 当开发者发现有机会应用成熟的设计模式时,它可以为重构过程提供一个更高层次的目标。然而,这仍然主要是一种从技术角度看待设计质量的视角。
But nearly all the literature on how to refactor focuses on mechanical changes to the code that make it easier to read or to enhance at a very detailed level. The approach of “refactoring to patterns”1 can give a higher-level target to the refactoring process when a developer recognizes an opportunity to apply an established design pattern. Still, it is a primarily technical view of the quality of a design.
对系统可行性影响最大的重构,往往是那些源于对领域的新洞察,或是那些通过代码阐明模型表达方式的重构。这类重构绝非取代基于设计模式的重构或微重构(后者应持续进行),而是在此基础上增加了一个层次:重构到更深层次的模型。基于领域洞察的重构通常包含一系列微重构,但其动机并非仅仅在于代码的现状。微重构提供了便捷的变更单元,引导我们构建更具洞察力的模型。其目标是,开发者不仅能够理解代码的功能,还能理解其背后的原因,并将这些原因与和领域专家的持续沟通联系起来。
The refactorings that have the greatest impact on the viability of the system are those motivated by new insights into the domain or those that clarify the model’s expression through the code. This type of refactoring does not in any way replace the refactorings to design patterns or the micro-refactorings, which should proceed continuously. It superimposes another level: refactoring to a deeper model. Executing a refactoring based on domain insight often involves a series of micro-refactorings, but the motivation is not just the state of the code. Rather, the micro-refactorings provide convenient units of change toward a more insightful model. The goal is that not only can a developer understand what the code does; he or she can also understand why it does what it does and can relate that to the ongoing communication with the domain experts.
《重构》 (Fowler 1999)中的目录涵盖了大多数常见的微重构。每种微重构的主要动机都源于代码本身存在的某些问题。相比之下,领域模型会随着新见解的出现而发生各种各样的变化,因此不可能编制一份全面的目录。
The catalog in Refactoring (Fowler 1999) covers most of the micro-refactorings that come up regularly. Each is motivated primarily by some problem that can be observed in the code itself. By contrast, domain models are transformed in such a range of ways as new insights emerge that a comprehensive catalog would be impossible to compile.
建模本质上与任何探索一样,都是非结构化的。无论学习和深度思考将我们引向何方,重构模型以获得更深刻的洞察都应随之而来。正如第11章所述,已发布的成功模型集合会有所帮助,但我们不应偏离正轨,试图将领域建模简化为一套固定的模式或工具包。建模和设计需要创造力。接下来的六章将提出一些具体的思路,帮助我们思考如何改进领域模型,以及如何通过设计赋予模型生命力。
Modeling is as inherently unstructured as any exploration. Refactoring to deeper insight should follow wherever learning and deep thinking lead. Published collections of successful models can be helpful, as discussed in Chapter 11, but we shouldn’t get sidetracked trying to reduce domain modeling to a cookbook or a toolkit. Modeling and design call for creativity. The next six chapters will suggest some specific approaches to thinking about improving a domain model, along with the design that brings it to life.
传统的对象分析解释方法是,在需求文档中识别名词和动词,并将它们用作初始对象和方法。这种解释被认为是一种过于简化的方法,但对于教授对象建模仍然有用。初学者。但事实是,最初的模型通常是幼稚和肤浅的,基于浅薄的知识。
The traditional way of explaining object analysis involves identifying nouns and verbs in the requirements documents and using them as the initial objects and methods. This explanation is recognized as an oversimplification that can be useful for teaching object modeling to beginners. The truth is, though, that initial models usually are naive and superficial, based on shallow knowledge.
例如,我曾经参与开发一款航运应用,最初设想的对象模型涉及船舶和集装箱。船舶在不同地点之间航行,集装箱则通过装卸作业进行关联和分离。这准确地描述了一些实际的航运活动。但事实证明,对于航运业务软件而言,这并非一个有效的模型。
For example, I once worked on a shipping application for which my initial idea of an object model involved ships and containers. Ships moved from place to place. Containers were associated and disassociated through load and unload operations. That is an accurate description of some physical shipping activities. It does not turn out to be a very useful model for shipping business software.
最终,经过数月与航运专家反复磋商,我们开发出一种截然不同的模式。这种模式对普通人来说可能不太容易理解,但对专家而言却更具实用价值。它重新聚焦于货物运输业务本身。
Eventually, after months working with shipping experts through many iterations, we evolved a quite different model. It was less obvious to a layperson, but much more relevant to the experts. It was refocused on the business of delivering cargo.
船舶依然存在,但被抽象化为“船舶航次”,即为船舶、火车或其他运输工具安排的特定行程。船舶本身是次要的,可以在最后一刻因维护或延误而进行替换,而船舶航次则按计划进行。集装箱几乎从模型中消失了。它确实以一种截然不同、极其复杂的形式出现在货物装卸应用中,但在最初的应用场景中,集装箱只是一个操作细节。货物的实际移动让位于货物法律责任的转移。一些不太显眼的物品,例如“提单”,则凸显出来。
The ships were still there, but abstracted in the form of a “vessel voyage,” a particular trip scheduled for a ship, train, or other carrier. The ship itself was secondary, and could be substituted at the last minute for maintenance or a slipping schedule, while the vessel voyage went on as planned. The shipping container all but disappeared from the model. It did emerge in a cargo-handling application in a different, very complex form, but in the context of the original application, the container was an operational detail. The physical movement of the cargo took a back seat to the transfers of legal responsibility for that cargo. Less obvious objects, such as the “bill of lading,” came to the fore.
每当有新的对象建模师加入项目,他们的第一个建议是什么?缺少的类:船舶和集装箱。他们都很聪明,只是没有经历过探索的过程。
Whenever new object modelers showed up on the project, what was their first suggestion? The missing classes: ship and container. They were smart people. They just hadn’t gone through the processes of discovery.
深度模型清晰地表达了领域专家的主要关注点及其最相关的知识,同时剔除了领域的表面特征。这个定义没有提及抽象。深度模型通常包含抽象元素,但也可能包含直击问题核心的具体元素。
A deep model provides a lucid expression of the primary concerns of the domain experts and their most relevant knowledge while it sloughs off the superficial aspects of the domain. This definition doesn’t mention abstraction. A deep model usually has abstract elements, but it may well have concrete elements where those cut to the heart of the problem.
通用性、简洁性和解释力源于真正契合领域的模型。这类模型几乎都具备的一个特点是:使用简洁明了(尽管可能略显抽象)的语言,而这正是业务专家乐于使用的。
Versatility, simplicity, and explanatory power come from a model that is truly in tune with the domain. One feature such models almost always have is a simple, though possibly abstract, language that the business experts like to use.
在不断重构的过程中,设计本身需要能够支持变更。第十章探讨了如何使设计易于维护,无论对于变更人员还是将其与其他系统部分集成的人员而言都是如此。
In a process of constant refactoring, the design itself needs to support change. Chapter 10 looks at ways to make a design easy to work with, both for those changing it and for those integrating it with other parts of the system.
某些设计特性使其更易于修改和使用。这些特性并不复杂,但却具有挑战性。“灵活设计”及其实现方法是第十章的主题。
Certain characteristics of a design make it easier to change and use. They are not complicated, but they are challenging. “Supple design” and ways to approach it are the subjects of Chapter 10.
幸运的是,反复修改模型和代码——如果每次修改都反映了新的理解——就能在最需要改变的地方带来灵活性,同时也能简化常见操作。一副戴得久的手套,手指弯曲的地方会变得柔软,而其他部位则保持坚硬,起到保护作用。因此,尽管这种建模和设计方法需要大量的试错,但修改实际上会变得更加容易,而反复的修改最终会推动我们实现更灵活的设计。
One bit of luck is that the very act of transforming the model and code again and again—if each change reflects new understanding—can bring about flexibility at just the points where change is most needed, along with easy ways of doing the common things. A well-worn glove becomes supple at the points where the fingers bend, while other parts are stiff and protective. So although there is a lot of trial and error involved in this approach to modeling and design, the changes can actually become easier to make, and the repeated changes actually move us toward a supple design.
除了促进变革,灵活的设计还有助于模型本身的完善。模型驱动设计建立在两方面之上。深厚的模型为富有表现力的设计提供了可能。同时,当设计足够灵活,允许开发者进行实验,并清晰地向开发者展示其运行机制时,它就能为模型探索过程提供洞见。反馈循环的这一部分至关重要,因为我们所寻求的模型不仅仅是一套美好的理念:它是系统的基石。
In addition to facilitating change, a supple design contributes to the refinement of the model itself. A MODEL-DRIVEN DESIGN stands on two legs. A deep model makes possible an expressive design. At the same time, a design can actually feed insight into the model discovery process when it has the flexibility to let a developer experiment and the clarity to show a developer what is happening. This half of the feedback loop is essential, because the model we are looking for is not just a nice set of ideas: it is the foundation of the system.
要设计出真正适合当前问题的设计,首先必须建立一个能够捕捉领域核心相关概念的模型。积极寻找这些概念并将它们融入设计中,正是第九章“将隐性概念显性化”的主题。
To create a design really fitted to the problem at hand, you must first have a model that captures the central relevant concepts of the domain. Actively searching for these concepts and bringing them into the design is the subject of Chapter 9, “Making Implicit Concepts Explicit.”
由于模型与设计密切相关,当代码难以重构时,建模过程就会停滞不前。第十章“灵活的设计”讨论了如何为软件开发人员(包括您自己)编写软件,以便能够高效地扩展软件。并进行调整。这项工作与模型的进一步完善密不可分。它通常需要更先进的设计技术和更严谨的模型定义。
Because of the close relationship between model and design, the modeling process comes to a halt when the code is hard to refactor. Chapter 10, “Supple Design,” discusses how to write software for software developers, not least yourself, so that it is productive to extend and change. This effort goes hand in hand with further refinements to the model. It often entails more advanced design techniques and more rigor in model definitions.
通常情况下,你需要依靠创造力和反复试验来找到构建概念模型的有效方法,但有时也会有人提供现成的模式供你参考。第 11 章和第12 章讨论了“分析模式”和“设计模式”的应用。这些模式并非现成的解决方案,但它们可以帮助你进行知识分析,并缩小你的搜索范围。
You will usually depend on creativity and trial and error to find good ways to model the concepts you discover, but sometimes someone has laid down a pattern you can follow. Chapters 11 and 12 discuss the application of “analysis patterns” and “design patterns.” Such patterns are not ready-made solutions, but they feed your knowledge crunching process and narrow your search.
但我将从领域驱动设计中最激动人心的事件开始第三部分。有时,当模型驱动设计和明确的概念都已就绪时,突破就会发生。一个机会摆在眼前,让你的软件变得比预期更具表现力和通用性。这可能意味着新增功能,也可能仅仅意味着用一个更简洁、更灵活的代码来表达更深层次的模型,从而替换掉一大段僵化的代码。虽然这样的突破并非每天都会出现,但它们弥足珍贵,因此一旦发生,就必须识别并抓住这个机会。
But I’ll start Part III with the most exciting event in domain-driven design. Sometimes, when the stage is set with a MODEL-DRIVEN DESIGN and explicit concepts, you have a breakthrough. An opportunity opens up to transform your software into something more expressive and versatile than you expected. This can mean new features or it can just mean the replacement of a big chunk of rigid code with a simple, flexible expression of a deeper model. Although such breakthroughs don’t come along every day, they are so valuable that when they do happen, the opportunity needs to be recognized and grasped.
第八章讲述了一个项目的真实故事,该项目通过重构来加深理解,最终取得了突破性进展。这种经历并非可以预先计划的。尽管如此,它为思考领域重构提供了一个很好的背景。
Chapter 8 tells the true story of a project on which a process of refactoring toward deeper insight led to a breakthrough. This experience is not something you can plan for. Nonetheless, it provides a good context for thinking about domain refactoring.
重构带来的回报并非线性增长。通常,投入少量精力会带来边际收益,而这些微小的改进累积起来就能产生显著效果。它们能够对抗熵增,是防止代码僵化、沦为“化石”的第一道防线。但有些最重要的洞见往往突如其来,给项目带来巨大的冲击。
The returns from refactoring are not linear. Usually there is a marginal return for a small effort, and the small improvements add up. They fight entropy, and they are the frontline protection against a fossilized legacy. But some of the most important insights come abruptly and send a shock through the project.
团队循序渐进地吸收知识,并将其整合到一个模型中。深度模型可以通过一系列小的重构逐步形成,一次重构一个对象:这里调整一下关联关系,那里调整一下职责。
Slowly but surely, the team assimilates knowledge and crunches it into a model. Deep models can emerge gradually through a sequence of small refactorings, an object at a time: a tweaked association here, a shifted responsibility there.
然而,持续重构往往会带来一些不那么有序的情况。每次对代码和模型的改进都能让开发者更清晰地了解情况。这种清晰性为突破性进展创造了可能。洞察。变革浪潮催生出一个更深层次契合用户实际情况和优先事项的模型。在复杂性消融的同时,模型的通用性和解释力却突飞猛进。
Often, though, continuous refactoring prepares the way for something less orderly. Each refinement of code and model gives developers a clearer view. This clarity creates the potential for a breakthrough of insights. A rush of change leads to a model that corresponds on a deeper level to the realities and priorities of the users. Versatility and explanatory power suddenly increase even as complexity evaporates.
这种突破并非一种技巧,而是一种事件。挑战在于如何识别正在发生的事情并决定如何应对。为了传达这种体验的感受,我将讲述几年前我参与的一个项目的真实故事,以及我们如何最终构建出一个非常有价值的深度模型。
This sort of breakthrough is not a technique; it is an event. The challenge lies in recognizing what is happening and deciding how to deal with it. To convey what this experience feels like, I’ll tell a true story of a project I worked on some years ago, and how we arrived at a very valuable deep model.
经过纽约漫长的冬季重构,我们最终得到了一个模型,它不仅捕捉到了该领域的一些关键知识,而且其设计也确实能为应用程序带来实际效益。当时我们正在开发一个大型应用程序的核心部分,该应用程序用于管理投资银行的银团贷款。
After a long New York winter of refactoring, we had arrived at a model that captured some of the key knowledge of the domain and a design that did some real work for the application. We were developing a core part of a large application for managing syndicated loans in an investment bank.
当英特尔想要建造一座价值十亿美元的工厂时,他们需要一笔数额巨大的贷款,任何一家贷款公司都无法独自承担。因此,贷款方组成银团,汇集资源共同支持这项融资项目(参见侧边栏)。投资银行通常担任银团牵头方,负责协调交易和其他服务。我们的项目就是开发一款软件来跟踪和支持整个流程。
When Intel wants to build a billion-dollar factory, they need a loan that is too big for any single lending company to take on, so the lenders form a syndicate that pools its resources to support a facility (see sidebar). An investment bank usually acts as syndicate leader, coordinating transactions and other services. Our project was to build software to track and support this whole process.
我们当时感觉还不错。四个月前,我们还深陷于一个完全无法运行的继承代码库的困境中,但我们已经努力将其改造为一个连贯的模型驱动设计。
We were feeling pretty good. Four months before, we had been in deep trouble with a completely unworkable, inherited code base, which we had since wrestled into a coherent MODEL-DRIVEN DESIGN.
图 8.1所示的模型将常见情况简化为:贷款投资是一个派生对象,代表特定投资者对贷款的出资额,该出资额与其在融资安排中的份额成正比。
The model reflected in Figure 8.1 makes the common case very simple. The Loan Investment is a derived object that represents a particular investor’s contribution to the Loan, proportional to its share in the Facility.
图 8.1. 假设贷款人股份固定的模型
Figure 8.1. A model that assumes lender shares are fixed
但其中也出现了一些令人不安的迹象。我们不断遇到意料之外的要求,这些要求使设计变得复杂。一个主要的例子是,我们逐渐意识到,融资协议中的份额仅仅是参与特定贷款提取的指导原则。当借款人提出贷款申请时,银团的牵头人会召集所有成员认领各自的份额。
But there were some disconcerting signs. We kept stumbling over unexpected requirements that complicated the design. A major example was the creeping understanding that the shares in a Facility were only a guideline to participation in any particular loan draw-down. When the borrower requests its money, the leader of the syndicate calls all members for their shares.
当被要求出资时,投资者通常会支付他们的份额,但他们也常常会与其他投资集团成员协商,减少(或增加)投资额。我们通过在模型中加入贷款调整机制来应对这种情况。
When called, the investors usually cough up their share, but often they negotiate with other members of the syndicate and invest less (or more). We had accommodated this by adding Loan Adjustments to the model.
图 8.2. 逐步修改以解决问题的模型。贷款调整跟踪贷款人最初在融资安排中同意的份额的偏差。
Figure 8.2. A model incrementally changed to solve problems. Loan Adjustments track departures from the share a lender originally agreed to in the Facility.
随着各种交易规则日趋清晰,这类改进使我们能够跟上步伐。但复杂性不断增加,我们似乎无法迅速实现真正可靠的功能。
Refinements of this kind allowed us to keep up as the rules of various transactions became clearer. But complexity was increasing, and we did not seem to be converging quickly onto really solid functionality.
更令人担忧的是,我们用日益复杂的算法也无法消除一些细微的舍入误差。诚然,在一笔1亿美元的交易中,没人会在意那几美分的差额,但银行家们不会信任那些无法做到这一点的软件。我们必须仔细核算每一分钱。我们开始怀疑,我们遇到的困难是基本设计缺陷的征兆。
Even more troubling were subtle rounding inconsistencies that we had not been able to squash with increasingly complex algorithms. True, in a $100 million (MM) deal, no one cares about where the extra pennies go, but bankers don’t trust software that cannot meticulously account for those pennies. We began to suspect that our difficulties were symptomatic of a basic design problem.
突然有一天,我们恍然大悟,原来问题出在哪里。我们的模型将融资和贷款份额绑定在一起的方式并不适合业务需求。这一发现产生了广泛的影响。业务专家们纷纷点头,热情地提供帮助——我敢说,他们肯定也在纳闷我们怎么花了这么长时间——我们在白板上敲定了一个新的模型。虽然细节尚未完全敲定,但我们已经明确了新模型的关键特征:贷款份额和融资份额可以彼此独立地变动。有了这一认识,我们利用新模型的可视化图表模拟了各种场景,图表大致如下:
Suddenly one week it dawned on us what was wrong. Our model tied together the Facility and Loan shares in a way that was not appropriate to the business. This revelation had wide repercussions. With the business experts nodding, enthusiastically helping—and, I dare say, wondering what took us so long—we hashed out a new model on a whiteboard. Although the details hadn’t jelled yet, we knew the crucial feature of the new model: shares of the Loan and those of the Facility could change independently of each other. With that insight, we walked through numerous scenarios using a visualization of the new model that looked something like this:
图 8.3. 基于设施份额的提款分配
Figure 8.3. A drawdown distributed based on Facility shares
该图显示,借款人选择从该融资协议承诺的1亿美元中提取初始资金5000万美元。三家贷款机构按其在融资协议中的份额比例分别出资,最终形成一笔5000万美元的贷款,由三家贷款机构共同承担。
This diagram says that the borrower has chosen to draw an initial $50MM from the $100MM committed under the Facility. The three lenders chip in their shares in exact proportion to the Facility shares, resulting in a $50MM Loan divided among the lenders.
然后,如图 8.4所示,借款人额外提取了3000万美元,使其未偿贷款总额达到8000万美元,仍低于该融资机制1亿美元的限额。此次,B公司选择不参与,A公司因此获得额外份额。提取份额反映了这些投资选择。当提取金额计入贷款总额后,贷款份额与融资机制份额不再成比例。这种情况很常见。
Then, in Figure 8.4, the borrower draws an additional $30MM, bringing his outstanding Loan to $80MM, still under the $100MM limit of the Facility. This time, Company B chooses not to participate, letting Company A take an extra share. The shares of the draw-down reflect these investment choices. When the drawdown amounts are added to the Loan, the shares of the Loan are no longer proportional to the shares of the Facility. This is common.
图 8.4. 贷款人 B 选择不进行第二次提款。
Figure 8.4. Lender B opts out of a second drawdown.
图 8.5. 本金支付总是按未偿贷款份额的比例分配。
Figure 8.5. Principal payments are always distributed proportional to shares in the outstanding Loan.
借款人偿还贷款时,剩余款项将根据贷款份额而非融资额度在各贷款人之间分配。同样,利息支付也将根据贷款份额进行分配。
When the borrower pays down the Loan, the money is divided among the lenders according to the shares of the Loan, not the Facility. Likewise, interest payments will be divided according to the Loan shares.
图 8.6. 费用支付总是按基金份额的比例分配。
Figure 8.6. Fee payments are always distributed proportionally to shares in the Facility.
另一方面,当借款人支付使用贷款服务的费用时,这笔费用会根据贷款份额进行分配,而与实际出借资金的是谁无关。贷款本身不会因费用支付而改变。甚至在某些情况下,贷款人会将费用份额与其利息份额分开交易,等等。
On the other hand, when the borrower pays a fee for the privilege of having the Facility available, this money is divided according to the Facility shares, regardless of who actually has lent money. The Loan is unchanged by fee payments. There are even scenarios in which lenders trade shares of fees separately from their shares of interest, and so on.
我们获得了两个深刻的领悟。首先,我们意识到我们的“投资”和“贷款投资”只是一个普遍而基本概念的两个特例:份额。融资工具的份额、贷款的份额、支付分配的份额。份额,无处不在。任何可分割价值的份额。
We had two deep insights. First was the realization that our “Investments” and “Loan Investments” were just two special cases of a general and fundamental concept: shares. Shares of a facility, shares of a loan, shares of a payment distribution. Shares, shares everywhere. Shares of any divisible value.
几天动荡之后,我根据与专家讨论中使用的语言以及我们共同探讨的情景,勾勒出了一个股票模型。
A few tumultuous days later I had sketched a model of shares, drawing on the language used in the discussions with experts and the scenarios we had explored together.
图 8.7. 股份的抽象模型
Figure 8.7. An abstract model of shares
我还绘制了一个新的贷款模型草图与之配套。
I also sketched a new loan model to go with it.
图 8.8.使用份额饼图的贷款模型
Figure 8.8. The Loan model using Share Pie
不再有专门用于表示融资或贷款份额的对象。它们都被分解成更直观的“份额饼图”。这种概括使得“份额数学”得以引入,极大地简化了任何交易中份额的计算,并使这些计算更具表现力、更简洁,且易于组合。
There were no longer specialized objects for the shares of a Facility or a Loan. They both were broken down into the more intuitive “Share Pie.” This generalization allowed the introduction of “shares math,” vastly simplifying the calculation of shares in any transaction, and making those calculations more expressive, concise, and easily combined.
但最重要的是,问题得以解决,是因为新模型移除了一个不恰当的限制。它允许贷款份额偏离融资份额的比例,同时保留了总额、费用分配等方面的有效限制。贷款份额可以直接调整,因此不再需要贷款调整,并且省去了大量特殊情况逻辑。
But most of all, problems went away because the new model removed an inappropriate constraint. It freed the Loan’s Shares to depart from the proportions of the Facility’s Shares, while keeping in place the valid constraints on totals, fee distributions, and so on. The Share Pie of the Loan could be adjusted directly, so the Loan Adjustment was no longer needed, and a large amount of special-case logic was eliminated.
贷款投资项目消失了,这时我们才意识到“贷款投资”并非银行术语。事实上,业务专家曾多次表示他们不理解这个概念。他们相信我们的软件知识,并认为它对技术设计有用。但实际上,我们是基于对这个领域理解的不足而创建了这个概念。
The Loan Investment had disappeared, and at this point we realized that “loan investment” was not a banking term. In fact, the business experts had told us a number of times that they didn’t understand it. They had deferred to our software knowledge and assumed it was useful to the technical design. Actually, we had created it based on our incomplete understanding of the domain.
突然间,基于这种全新的领域视角,我们能够相对轻松地梳理之前遇到的所有场景,比以往任何时候都更加简单。我们的模型图对业务专家来说也变得清晰易懂,他们之前常常表示这些图表对他们来说“太技术化”。即使只是在白板上画个草图,我们也能看到那些最棘手的舍入问题将被彻底解决,从而可以省去一些复杂的舍入代码。
Suddenly, on the basis of this new way of looking at the domain, we could run through every scenario we had ever encountered relatively effortlessly, much more simply than ever before. And our model diagrams made perfect sense to the business experts, who had often indicated that the diagrams were “too technical” for them. Even just sketching on a whiteboard, we could see that our most persistent rounding problems would be pulled out by the roots, allowing us to scrap some of the complicated rounding code.
我们的新模式效果很好。真的非常好。
Our new model worked well. Really, really well.
我们都感到不舒服!
And we all felt sick!
你或许会理所当然地认为我们此时应该欣喜若狂。然而事实并非如此。我们面临着紧迫的期限;项目进度已经严重滞后。我们最主要的情绪是恐惧。
You might reasonably assume that we would have been elated at this point. We were not. We were under a severe deadline; the project was already dangerously behind schedule. Our dominant emotion was fear.
重构的精髓在于循序渐进,始终保持所有功能正常运行。但要将我们的代码重构为这种新模型,就需要修改大量的支持代码,而且中间几乎没有稳定的停顿点。我们可以看到一些可以改进的小地方,但这些改进都无法让我们更接近新概念。我们可以看到一系列通往目标的小步骤,但在此过程中,应用程序的部分功能将被禁用。而且,这还是在自动化测试尚未广泛应用于此类项目的时代。我们当时没有任何自动化测试,因此必然会出现一些意想不到的故障。
The gospel of refactoring is that you always go in small steps, always keeping everything working. But to refactor our code to this new model would require changing a lot of supporting code, and there would be few, if any, stable stopping points in between. We could see some small improvements we could make, but none that would take us closer to the new concept. We could see a sequence of small steps to get there, but parts of the application would be disabled along the way. And this was before the age when automated tests were widely used on such projects. We had none, so there was bound to be unforeseen breakage.
而且这需要付出努力。几个月来我们一直奔波劳碌,已经筋疲力尽了。
And it was going to take effort. We were already exhausted from months of pushing.
这时,我们和项目经理进行了一次我永生难忘的会面。我们的经理是一位既聪明又大胆的人。他问了一系列问题:
At this point, we had a meeting with our project manager that I will never forget. Our manager was an intelligent and bold man. He asked a series of questions:
Q: How long would it take to get back to current functionality with the new design?
Q: Could we solve the problems without it?
A: Probably. But no way to be sure.
Q: Would we be able to move forward in the next release if we didn’t do it now?
答:如果不做出改变,前进的步伐将会非常缓慢。而且,一旦我们已经拥有了一定的用户基础,改变将会变得更加困难。
A: Forward movement would be slow without the change. And the change would be much harder once we had an installed base.
Q: Did we think it was the right thing to do?
答:我们知道当时的政治局势不稳定,所以如果需要的话,我们会想办法应对。而且我们当时也很疲惫。但是,是的,这是一个更简单、更适合公司业务的解决方案。从长远来看,风险也更低。
A: We knew the political situation was unstable, so we’d cope if we had to. And we were tired. But, yes, it was a simpler solution that fit the business much better. In the long run it was lower risk.
他给了我们许可,并告诉我们他会应对一切压力。我一直非常钦佩他做出这个决定所需的勇气和信任。
He gave us the go-ahead and told us he would handle the heat. I’ve always had tremendous admiration for the courage and trust it took for him to make that decision.
我们拼尽全力,三周就完成了。这的确是个大工程,但进展却出奇地顺利。
We busted our butts and got it done in three weeks. It was a big job, but it went surprisingly smoothly.
那些令人费解的、出乎意料的需求变更停止了。舍入逻辑虽然从来都不简单,但已经稳定下来并变得简单易行。确实如此。我们交付了第一版,第二版的开发也顺理成章。我差点就崩溃了。
The mystifyingly unexpected requirement changes stopped. The rounding logic, though never exactly simple, stabilized and made sense. We delivered version one and the way was clear to version two. My nervous breakdown was narrowly averted.
随着第二版的不断演进,这个“共享饼图”成为了整个应用程序的统一主题。技术人员和业务专家用它来讨论系统,市场人员用它来向潜在客户解释功能。这些潜在客户和现有客户很快就掌握了它,并用它来讨论功能。它真正成为了通用语言的一部分,因为它直击贷款银团的核心。
As version two evolved, this Share Pie became the unifying theme of the whole application. Technical people and business experts used it to discuss the system. Marketing people used it to explain the features to prospective customers. Those prospects and customers immediately grasped it and used it to discuss features. It truly became part of the UBIQUITOUS LANGUAGE because it got to the heart of what loan syndication is about.
当面临向更深层次模型突破的机遇时,人们往往会感到恐惧。这种变革比大多数重构更具机遇,风险也更高。而且,时机可能并不合适。
When the prospect of a breakthrough to a deeper model presents itself, it is often scary. Such a change has higher opportunity and higher risk than most refactorings. And timing may be inopportune.
尽管我们可能希望并非如此,但进步并非一帆风顺。向真正深度模型的过渡意味着思维方式的深刻转变,并需要对设计进行重大调整。在许多项目中,模型和设计方面最重要的进展都来自于这些突破。
Much as we might like it to be otherwise, progress isn’t a smooth ride. The transition to a really deep model is a profound shift in your thinking and demands a major change to the design. On many projects the most important progress in model and design come in these breakthroughs.
不要因为急于求成而裹足不前。突破往往是在多次适度重构之后才出现的。大部分时间都花在了进行零散的改进上,模型洞察会在每次迭代过程中逐渐涌现。
Don’t become paralyzed trying to bring about a breakthrough. The possibility usually comes after many modest refactorings. Most of the time is spent making piecemeal improvements, with model insights emerging gradually during each successive refinement.
为了取得突破,应集中精力进行知识分析,并培养一种强大的通用语言。深入探究重要的领域概念,并在模型中明确表达出来(如第9章所述)。改进设计,使其更加灵活(参见第10章)。提炼模型(参见第15章)。着力推进这些更可预测的环节,以提高清晰度——这通常是取得突破的先兆。
To set the stage for a breakthrough, concentrate on knowledge crunching and cultivating a robust UBIQUITOUS LANGUAGE. Probe for important domain concepts and make them explicit in the model (as discussed in Chapter 9). Refine the design to be suppler (see Chapter 10). Distill the model (see Chapter 15). Push on these more predictable levers, which increase clarity—usually a precursor of breakthroughs.
不要因为小的改进而止步不前,即使这些改进仍然局限于同一个总体概念框架内,它们也能逐步深化模型。不要因为目光过于长远而裹足不前。只需时刻留意机会即可。
Don’t hold back from modest improvements, which gradually deepen the model, even if confined within the same general conceptual framework. Don’t be paralyzed by looking too far forward. Just be watchful for the opportunity.
那项突破让我们摆脱了困境,但这并非故事的终点。更深层次的模型带来了意想不到的机遇,使应用程序更加丰富,设计更加清晰。
That breakthrough got us out of the woods, but it was not the end of the story. The deeper model opened unexpected opportunities to make the application richer and the design clearer.
在Share Pie版本软件发布几周后,我们注意到模型中另一个令人困惑的方面,它使设计变得复杂。一个重要的实体缺失了,它的缺席导致其他对象不得不承担额外的职责。具体来说,贷款提取、费用支付等方面都有重要的规则,而所有这些逻辑都被塞进了“融资”和“贷款”的各种方法中。这些设计问题在Share Pie版本出现之前几乎难以察觉,但随着我们视野的开阔,它们变得显而易见。现在,我们注意到讨论中出现了一些在模型中根本找不到的术语——例如“交易”(指金融交易)——我们开始意识到,这些术语实际上是由那些复杂的方法隐含的。
Just weeks after the release of the Share Pie version of the software, we noticed another awkward aspect of the model that was complicating the design. An important ENTITY was missing, its absence leaving extra responsibilities to be taken up by other objects. Specifically, there were significant rules governing loan drawdowns, fee payments, and so on, and all this logic was crammed into various methods on the Facility and Loan. These design problems, which had been barely noticeable before the Share Pie breakthrough, became obvious with our clearer field of vision. Now we noticed terms popping up in our discussions that were nowhere to be found in the model—terms such as “transaction” (meaning a financial transaction)—that we started to realize were being implied by all those complicated methods.
遵循与之前所述类似的流程(值得庆幸的是,时间压力小得多),我们获得了新一轮的洞见,并构建了一个更深入的模型。这个新模型将那些隐含的概念明确化为交易类型,同时简化了头寸(包括融资和贷款在内的抽象概念)。这样一来,定义我们所从事的各种交易就变得容易多了。他们的规则、谈判程序和审批流程,以及所有相对容易理解的代码,都包含在内。
Following a process similar to the one described earlier (although, thankfully, under much less time pressure) led to yet another round of insights and a still deeper model. This new model made those implicit concepts explicit, as kinds of Transactions, and at the same time simplified the Positions (an abstraction including the Facility and Loan). It became easy to define the diverse transactions we had, along with their rules, negotiating procedures, and approval processes, and all in relatively self-explanatory code.
图 8.9。几周后,模型又取得了另一项突破。交易约束可以轻松精确地表达。
Figure 8.9. Another model breakthrough that followed several weeks later. Constraints on Transactions could be expressed with easy precision.
正如在真正突破到深度模型之后经常发生的那样,新设计的清晰性和简洁性,再加上基于新的通用语言的增强型沟通,带来了又一次建模突破。
As is often the case after a real breakthrough to a deep model, the clarity and simplicity of the new design, combined with the enhanced communication based on the new UBIQUITOUS LANGUAGE, had led to yet another modeling breakthrough.
在大多数项目因已建成工程的规模和复杂性而开始陷入停滞之际,我们的开发速度却在加快。
Our pace of development was accelerating at a stage where most projects are beginning to bog down in the mass and complexity of what has already been built.
深度建模听起来很棒,但究竟该如何实现呢?深度模型之所以强大,是因为它包含了核心概念和抽象,能够简洁灵活地表达用户活动、问题和解决方案的关键知识。第一步是在模型中以某种方式表示领域的基本概念。之后,经过反复的知识梳理和重构,才能进行模型的细化。但当一个重要的概念被识别出来并在模型和设计中明确表达出来时,这个过程才会真正启动。
Deep modeling sounds great, but how do you actually do it? A deep model has power because it contains the central concepts and abstractions that can succinctly and flexibly express essential knowledge of the users’ activities, their problems, and their solutions. The first step is to somehow represent the essential concepts of the domain in the model. Refinement comes later, after successive iterations of knowledge crunching and refactoring. But this process really gets into gear when an important concept is recognized and made explicit in the model and design.
当开发人员在讨论中意识到某个概念,或者在设计中隐含地表达某个概念时,就会发生许多领域模型及其对应代码的转换,然后他们会用一个或多个对象或关系在模型中明确地表示该概念。
Many transformations of domain models and the corresponding code happen when developers recognize a concept that has been hinted at in discussion or present implicitly in the design, and they then represent it explicitly in the model with one or more objects or relationships.
有时,将原本隐含的概念转化为显式概念,会成为构建深层模型的突破口。但更多时候,突破发生在模型中一些重要概念已经显式化之后;在多次重构反复调整其职责、改变其与其他对象的关系,甚至数次更改名称之后。一切最终豁然开朗。但这个过程始于以某种形式(无论多么粗糙)识别出隐含的概念。
Occasionally, this transformation of a formerly implicit concept into an explicit one is a breakthrough that leads to a deep model. More often, though, the breakthrough comes later, after a number of important concepts are explicit in the model; after successive refactorings have tweaked their responsibilities repeatedly, changed their relationships with other objects, and even changed their names a few times. Everything finally snaps into focus. But the process starts with recognizing the implied concepts in some form, however crude.
开发者必须对那些揭示潜在概念的线索保持敏感,有时甚至需要主动去寻找它们。大多数此类发现都来自于倾听团队的语言、仔细审视设计中的不协调之处和专家言论中看似矛盾的地方、挖掘领域文献以及进行大量的实验。
Developers have to sensitize themselves to the hints that reveal lurking implicit concepts, and sometimes they have to proactively search them out. Most such discoveries come from listening to the language of the team, scrutinizing awkwardness in the design and seeming contradictions in the statements of experts, mining the literature of the domain, and doing lots and lots of experimentation.
你可能有过这样的经历:用户总是提到报告中的某个项目。这个项目是由各种对象的属性汇总而成,甚至可能直接来自数据库查询。同样的数据集在应用程序的其他部分也被整合起来,用于展示、报告或推导某些内容。但你从未意识到需要用到某个对象。或许,你从未真正理解用户所说的某个术语的含义,也从未意识到它的重要性。
You may remember an experience like this: The users have always talked about some item on a report. The item is compiled from attributes of various objects and maybe even a direct database query. The same data set is assembled in another part of the application in order to present or report or derive something. But you have never seen the need for an object. Probably, you have never really understood what the users meant by a particular term and had not realized it was important.
突然,你灵光一闪。报告上的那个项目名称代表了一个重要的领域概念。你兴奋地和专家们讨论你的新发现。他们或许会松一口气,因为你终于明白了。或许他们会打个哈欠,因为他们一直以来都习以为常。无论如何,你开始在白板上绘制模型图,取代了以往那些手舞足蹈的解释。用户会纠正你新模型连接方式的细节,但你能感觉到讨论的质量有所提升。你和用户之间的理解更加精准,演示如何通过模型交互解决特定场景也变得更加自然。领域模型的语言变得更加有力。你重构代码以反映新模型,发现设计更加简洁清晰。
Then suddenly a light comes on in your head. The name of the item on that report designates an important domain concept. You talk excitedly with your experts about your new insight. Maybe they show relief that you finally got it. Maybe they yawn because they’ve taken it for granted all along. Either way, you start to draw model diagrams on the board that fill in for some hand waving that you’ve always done before. The users correct you on the details of how the new model connects, but you can tell that there is a change in the quality of the discussion. You and the users understand each other more precisely, and demonstrations of model interactions to solve specific scenarios have become more natural. The language of the domain model has become more powerful. You refactor the code to reflect the new model and find you have a cleaner design.
仔细聆听领域专家使用的语言。他们是否用简洁的词语概括了复杂的概念?他们是否会纠正你的用词(或许措辞委婉)?当你使用某个特定的短语时,他们脸上的困惑表情是否会消失?这些都暗示着某个概念可能对模型有所帮助。
Listen to the language the domain experts use. Are there terms that succinctly state something complicated? Are they correcting your word choice (perhaps diplomatically)? Do the puzzled looks on their faces go away when you use a particular phrase? These are hints of a concept that might benefit the model.
这并非传统的“名词是宾语”观念。听到一个新词会产生一个线索,你可以以此为基础展开对话。知识的梳理旨在提炼出一个清晰、实用的概念。当用户或领域专家使用设计中根本不存在的词汇时,这是一个警示信号。如果开发人员和领域专家都使用了设计中不存在的术语,那就更加令人担忧了。
This is not the old “nouns are objects” notion. Hearing a new word produces a lead, which you follow up with conversation and knowledge crunching, with the goal of carving out a clean, useful concept. When the users or domain experts use vocabulary that is nowhere in the design, that is a warning sign. It is a doubly strong warning when both the developers and the domain experts are using terms that are not in the design.
或许将其视为一种机遇更为恰当。“普适语言”指的是渗透于语音、文档、模型图乃至代码中的词汇。如果设计中缺少某个术语,那么就有机会通过添加该术语来改进模型和设计。
Or perhaps it is better to look at it as an opportunity. The UBIQUITOUS LANGUAGE is made up of the vocabulary that pervades speech, documents, model diagrams, and even code. If a term is absent from the design, it is an opportunity to improve the model and design by including it.
团队已经开发出一款可以预订货物的应用程序。他们正在着手构建一款“运营支持”应用程序,该程序将有助于管理始发地和目的地以及船舶间货物装卸的工作指令。
The team had already developed a working application that could book a cargo. They were starting to build an “operations support” application that would help juggle the work orders for loading and unloading cargos at the origin and destination and at transfers between ships.
该预订应用程序使用路线规划引擎来规划货物的运输路线。旅程的每一段都存储在数据库表的一行中,指示计划运输货物的船舶航次 ID(特定船舶的特定航次)、货物的装载地点和货物的卸载地点。
The booking application used a routing engine to plan the trip for a cargo. Each leg of the journey was stored in a row of a database table, indicating the ID of the vessel voyage (a particular voyage by a particular ship) slated to carry the cargo, the location where it would be loaded, and the location where it would be unloaded.
图 9.1
Figure 9.1
让我们来偷听一下开发人员和一位物流专家之间的对话(经过大量删减)。
Let’s eavesdrop on a conversation (heavily abbreviated) between the developer and a shipping expert.
开发人员:我想确保“货物预订”表包含运营应用程序所需的所有数据。
Developer: I want to make sure the “cargo bookings” table has all the data that the operations application will need.
专家:他们需要这架货运飞机的完整航程。它现在掌握了哪些信息?
Expert: They’re going to need the whole itinerary for the Cargo. What information does it have now?
开发人员:每段航程的货物 ID、船舶航次、装货港和卸货港。
Developer: The cargo ID, the vessel voyage, the loading port, and the unloading port for each leg.
专家:日期方面呢?运营部门需要根据预期时间安排承包工作。
Expert: What about the date? Operations will need to contract handling work based on the expected times.
开发人员:嗯,这可以从船舶航程计划中得出。表格数据已经过规范化处理。
Developer: Well, that can be derived from the schedule of the vessel voyage. The table data is normalized.
专家:是的,需要日期是正常的。运营人员会使用这类行程安排来规划接下来的搬运工作。
Expert: Yes, it is normal to need the date. Operations people use these kinds of itineraries to plan for upcoming handling work.
开发人员:是的……好的,他们肯定能获取到这些日期。运营管理应用程序将能够提供完整的装卸流程,包括每次装卸操作的日期。我想你可以称之为“行程安排”。
Developer: Yeah . . . OK, they’ll definitely have access to the dates. The operations management application will be able to provide the whole loading and unloading sequence, with the date of each handling operation. The “itinerary,” I guess you would say.
专家:好的。他们最需要的是行程单。其实,你知道,预订应用程序里有个菜单项可以打印行程单或者通过电子邮件发送给客户。你能不能用这个功能?
Expert: Good. The itinerary is the main thing they’ll need. Actually, you know, the booking application has a menu item that will print an itinerary or e-mail it to the customer. Can you use that somehow?
开发人员:我觉得那只是一份报告。我们不能以此为基础开发运维应用程序。
Developer: That’s just a report, I think. We won’t be able to base the operations application on that.
(开发者先是沉思,然后兴奋起来。)
[Developer looks thoughtful, then excited.]
开发人员:所以,这份行程单实际上是预订和运营之间的联系纽带。
Developer: So, this itinerary is really the link between booking and operations.
专家:是的,还有一些客户关系方面的问题。
Expert: Yes, and some customer relations, too.
开发人员:(在白板上画图)所以,您觉得它大概是这样的吗?
Developer: [Sketching a diagram on the whiteboard.] So would you say it is something like this?
图 9.2
Figure 9.2
专家:是的,基本正确。对于每个航段,您希望看到船舶航程、装卸地点和时间。
Expert: Yes, that looks basically right. For each leg you’d like to see the vessel voyage, the load and unload location, and time.
开发人员:创建航段对象后,它就可以从船舶航程计划中获取时间。我们可以将行程对象作为与运营应用程序的主要接口。我们可以重写行程报告以使用此对象,从而将领域逻辑重新引入领域层。
Developer: So once we create the Leg object, it can derive the times from the vessel voyage schedule. We can make the Itinerary object our main point of contact with the operations application. And we can rewrite that itinerary report to use this, so we’ll get the domain logic back into the domain layer.
专家:我没完全听懂,但你说得对,行程单的两个主要用途是在预订报告中和运营应用程序中。
Expert: I didn’t follow all of that, but you are right that the two main uses for the Itinerary are in the report in booking and in the operations application.
开发者:嗨!我们可以让路由服务接口返回行程对象,而不是将数据放入数据库表中。这样,路由引擎就不需要了解我们的数据库表了。
Developer: Hey! We can make the Routing Service interface return an itinerary object instead of putting the data in the database table. That way the routing engine doesn’t need to know about our tables.
专家:嗯?
Expert: Huh?
开发者:我的意思是,我会让路线规划引擎只返回一个行程单。然后,当预订的其他信息保存完毕时,预订应用程序就可以将行程单保存到数据库中。
Developer: I mean, I’ll make the routing engine just return an Itinerary. Then it can be saved in the database by the booking application when the rest of the booking is saved.
专家:你的意思是现在情况不是那样了?!
Expert: You mean it isn’t that way now?!
随后,开发人员与其他参与路由流程的开发人员进行了讨论。他们仔细探讨了模型的变更及其对设计的影响,并在必要时咨询了物流专家。最终,他们绘制出了图 9.3所示的流程图。
The developer then went off to talk with the other developers involved in the routing process. They hashed out the changes to the model and the implications for the design, calling on the shipping experts when needed. They came up with the diagram in Figure 9.3.
图 9.3
Figure 9.3
接下来,开发人员重构了代码以反映新模型。他们分两到三次快速重构完成,一周内就完成了所有工作,只有简化预订应用程序中的行程报告这一项例外,他们在接下来的一周初就完成了。
Next, the developers refactored the code to reflect the new model. They did it in a series of two or three refactorings, but in quick succession, within a week, except for simplifying the itinerary report in the booking application, which they took care of early the following week.
开发人员仔细聆听了航运专家的意见,注意到“行程安排”的概念对他来说至关重要。诚然,所有数据都已收集完毕,行程报告也隐含了相关行为,但将行程安排明确纳入模型,则带来了新的机遇。
The developer had been listening closely enough to the shipping expert to notice how important the concept of an “itinerary” was to him. True, all the data was already being collected, and the behavior was implicit in the itinerary report, but the explicit Itinerary as part of the model opened up opportunities.
重构为显式Itinerary对象的好处:
Benefits of refactoring to the explicit Itinerary object:
1.更清晰地定义路由服务的接口
1. Defining the interface of the Routing Service more expressively
2.将路由服务与预订数据库表解耦
2. Decoupling the Routing Service from the booking database tables
3.明确预订应用程序与运营支持应用程序之间的关系(行程对象的共享)
3. Clarifying the relationship between the booking application and the operations support application (the sharing of the Itinerary object)
4.减少重复工作,因为行程单会为预订报告和运营支持应用程序提供装卸时间。
4. Reducing duplication, because the Itinerary derives loading/unloading times for both the booking report and the operations support application
5.将预订报告中的域逻辑移除,并将其放置在隔离的域层中。
5. Removing domain logic from the booking report and placing it in the isolated domain layer
6.扩展普适语言,使开发人员与领域专家之间以及开发人员之间能够更精确地讨论模型和设计。
6. Expanding the UBIQUITOUS LANGUAGE, allowing a more precise discussion of the model and design between developers and domain experts and among the developers themselves
你需要的概念并非总是显而易见,不会在对话或文档中自然浮现。你可能需要深入挖掘,甚至另辟蹊径。而挖掘的重点往往在于设计中最棘手的部分:流程执行着难以解释的复杂操作;每一个新需求似乎都只会增加复杂性。
The concept you need is not always floating on the surface, emerging in conversation or documents. You may have to dig and invent. The place to dig is the most awkward part of your design. The place where procedures are doing complicated things that are hard to explain. The place where every new requirement seems to add complexity.
有时候,我们很难意识到某个概念的缺失。你可能已经拥有了完成所有工作的对象,但却发现有些东西缺失了。承担这些责任可能会让人感到尴尬。或者,即使你意识到缺少了某些东西,也可能无法找到理想的解决方案。
Sometimes it can be hard to recognize that there even is a missing concept. You may have objects doing all the work but find some of the responsibilities awkward. Or, if you do realize something is missing, a model solution may elude you.
现在,你需要积极地与领域专家沟通,让他们参与到探索过程中来。如果运气好,他们或许会乐于尝试各种想法,并对模型进行试验。如果运气不佳,你和你的开发团队就必须自己提出想法,并将领域专家作为验证者,密切观察他们脸上的反应,看他们是否感到不适或认同。
Now you have to actively engage the domain experts in the search. If you are lucky, they may enjoy playing with ideas and experimenting with the model. If you are not that lucky, you and your fellow developers will have to come up with the ideas, using the domain expert as a validator, watching for discomfort or recognition on his or her face.
下一个故事的背景设定在一个假想的金融公司,该公司投资于商业贷款和其他生息资产。一款用于追踪这些投资及其收益的应用程序一直在逐步完善,功能一个接一个地添加。每天晚上,其中一个组件会以批处理脚本的形式运行,计算当天所有的利息和费用,然后将其正确记录到公司的会计软件中。
The next story is set in a hypothetical financial company that invests in commercial loans and other interest-bearing assets. An application that tracks those investments and the earnings from them has been evolving incrementally, feature by feature. Each night, one component was to run as a batch script, calculating all interest and fees for the day and then recording them appropriately in the company’s accounting software.
图 9.4. 一个笨拙的模型
Figure 9.4. An awkward model
夜间批处理脚本会遍历每个资产,并告知每个资产calculateInterestForDate()当天执行特定操作。脚本获取收益值(即收入金额),并将该金额连同特定账簿的名称一起传递给提供会计程序公共接口的服务。该软件会将金额记入指定的账簿。脚本采用类似的流程从每个资产中获取当日费用,并将其记入不同的账簿。
The nightly batch script iterated through each Asset, telling each to calculateInterestForDate() on that day’s date. The script took the return value (the amount earned) and passed this amount, along with the name of a specific ledger, to a SERVICE that provided the public interface of the accounting program. That software posted the amount to the named ledger. The script went through a similar process to get the day’s fees from each Asset, posting them to a different ledger.
一位开发人员一直苦于应对日益复杂的利息计算问题。她开始怀疑是否存在更适合这项任务的模型。于是,她请教了自己最信任的领域专家,希望他能帮助她深入研究这个问题。
A developer had been struggling with the increasing complexity of calculating interest. She started to suspect an opportunity for a model better suited to the task. This developer asked her favorite domain expert to help her dig into the problem area.
开发者:我们的利息计算器快要失控了。
Developer: Our Interest Calculator is getting out of hand.
专家:这部分比较复杂。我们还有一些病例尚未公布。
Expert: That is a complicated part. We still have more cases we’ve been holding back.
开发人员:我知道。我们可以通过替换不同的利息计算器来添加新的利息类型。但我们现在遇到的最大问题是,当利息没有按时支付时,如何处理这些特殊情况。
Developer: I know. We can add new interest types by substituting a different Interest Calculator. But what we’re having the most trouble with right now is all these special cases when they don’t pay the interest on schedule.
专家:这些其实并不特殊。人们何时付款有很多灵活性。
Expert: Those really aren’t special cases. There’s a lot of flexibility in when people pay.
开发者:之前我们把利息计算器从资产中分离出来的时候,效果很好。我们可能需要进一步拆分它。
Developer: Back when we factored out the Interest Calculator from the Asset, it helped a lot. We may need to break it up more.
专家:好的。
Expert: OK.
开发者:我在想你或许有办法解释一下利息的计算方式。
Developer: I was thinking you might have a way of talking about this interest calculation.
专家:你是什么意思?
Expert: What do you mean?
开发人员:嗯,例如,我们正在追踪会计期间内应付但未付的利息。你们知道这叫什么吗?
Developer: Well, for example, we’re tracking the interest due but unpaid within an accounting period. Do you have a name for that?
专家:嗯,我们实际上不是那样做的。利息收入和利息支出是分开记账的。
Expert: Well, we don’t really do it like that. The interest earned and the payment are quite separate postings.
开发者:所以你不需要那个号码?
Developer: So you don’t need that number?
专家:嗯,有时候我们可能会考虑,但这并不是我们的经营方式。
Expert: Well, sometimes we might look at it, but it isn’t the way we do business.
开发人员:好的,如果付款和利息是分开的,也许我们应该这样建模。这样看起来怎么样?[在白板上画草图]
Developer: OK, so if the payment and interest are separate, maybe we should model them that way. How does this look? [Sketching on whiteboard]
图 9.5
Figure 9.5
专家:我猜也说得通。但你只是把它从一个地方搬到了另一个地方。
Expert: It makes sense, I guess. But you just moved it from one place to another.
开发者:不过现在利息计算器只记录已赚取的利息,而付款记录则单独保存该数字。虽然并没有简化很多,但这是否更能反映您的业务实践?
Developer: Except now the Interest Calculator only keeps track of interest earned, and the Payment keeps that number separately. It hasn’t simplified it a lot, but does it better reflect your business practice?
专家:啊,我明白了。我们也能有利息历史记录吗?就像付款历史记录一样。
Expert: Ah. I see. Could we have interest history, too? Like the Payment History.
开发者:是的,有人提出过这个新功能的需求。但其实原本可以添加到最初的设计中。
Developer: Yes, that has been requested as a new feature. But that could have been added onto the original design.
专家:哦。嗯,当我看到利息和付款记录这样分开显示时,我以为您是为了使利息的组织方式更像付款记录而将其拆分开来。您了解权责发生制会计吗?
Expert: Oh. Well, when I saw interest and Payment History separated like that, I thought you were breaking up the interest to organize it more like the Payment History. Do you know anything about accrual basis accounting?
开发者:请解释一下。
Developer: Please explain.
专家:每天,或者根据日程安排,我们都会将利息计入账簿。付款的记账方式则不同。您看到的这个汇总数据有点奇怪。
Expert: Each day, or whenever the schedule calls for, we have an interest accrual that gets posted to a ledger. The payments are posted a different way. This aggregate you have here is a little awkward.
开发人员:您的意思是,如果我们保留一份“应计项目”清单,就可以根据需要进行汇总或“过账”。
Developer: You’re saying that if we keep a list of “accruals,” they could be aggregated or . . . “posted” as needed.
专家:可能是在应计日记账,但确实,任何时间都可以汇总。费用的处理方式相同,当然记入不同的账簿。
Expert: Probably posted on the accrual date, but yes, aggregated anytime. Fees work the same way, posted to a different ledger, of course.
开发者:实际上,如果只计算一天或一个周期的利息,计算起来会更简单。那样我们就可以把所有利息都保留下来了。这样如何?
Developer: Actually, the interest calculation would be simpler if it was done just for one day, or period. And then we could just hang on to them all. How about this?
图 9.6
Figure 9.6
专家:当然。看起来不错。我不确定为什么这对你来说会更容易。但基本上,任何资产的价值都取决于它能产生的利息、费用等等。
Expert: Sure. It looks good. I’m not sure why this would be easier for you. But basically, what makes any asset valuable is what it can accrue in interest, fees, and so on.
开发者:你说过费用运作方式相同?它们……是什么来着……记入不同的账簿?
Developer: You said fees work the same way? They . . . what was it . . . post to different ledgers?
图 9.7
Figure 9.7
开发者:有了这个模型,我们就可以将利息计算(或者更准确地说,是利息计算器中的应计计算逻辑)与跟踪逻辑分离了。而且我之前一直没注意到费用计算器里有多少重复代码。此外,现在可以轻松添加各种类型的费用了。
Developer: With this model, we get the interest calculation, or rather, the accrual calculation logic that was in the Interest Calculator separated from tracking. And I hadn’t noticed until now how much duplication there is in the Fee Calculator. Also, now the different kinds of fees can easily be added.
专家:是的,之前的计算是正确的,但我现在可以看到所有细节了。
Expert: Yes, the calculation was correct before, but I can see everything now.
由于计算器类与其他设计部分没有直接耦合,因此重构起来相当容易。开发人员仅用了几个小时就用新语言重写了单元测试,并在第二天晚上就让新设计正常运行。最终结果如下。
Because the Calculator classes hadn’t been directly coupled with other parts of the design, this was a fairly easy refactoring. The developer was able to rewrite the unit tests to use the new language in a few hours and had the new design working late the next day. She ended up with this.
图 9.8. 重构后的更深层模型
Figure 9.8. A deeper model after refactoring
在重构后的应用程序中,每晚的批处理脚本会指示每个资产执行操作calculateAccrualsThroughDate()。返回值是一个应计项目集合,脚本会将每个应计项目的金额过账到指定的账簿中。
In the refactored application, the nightly batch script tells each Asset to calculateAccrualsThroughDate(). The return value is a collection of Accruals, each of whose amounts it posts to the indicated ledger.
新模型有几个优点。变化
The new model has several advantages. The change
1.用“累积”一词丰富了普遍使用的语言
1. Enriches the UBIQUITOUS LANGUAGE with the term “accrual”
2.将应计与支付脱钩
2. Decouples accrual from payment
3.将领域知识(例如要记账到哪个账本)从脚本转移到领域层。
3. Moves domain knowledge (such as which ledger to post to) from the script and into the domain layer
4.将费用和利息整合在一起,以符合业务需求并消除代码重复。
4. Brings fees and interest together in a way that fits the business and eliminates duplication in the code
5. 提供了一种简便的方法,可以将新的费用和利息形式作为应计计划添加。
5. Provides a straightforward path for adding new variations of fees and interest as Accrual Schedules
这一次,开发人员不得不深入挖掘所需的新概念。她意识到利息计算存在问题,并努力寻找更深层次的答案。
This time, the developer had to dig for the new concepts she needed. She could see the awkwardness of the interest calculations and made a committed effort to look for a deeper answer.
她很幸运能与一位才智过人、积极主动的银行专家合作。如果专家来源比较被动,她可能会经历更多次失败,并且更依赖其他开发人员作为头脑风暴伙伴。那样的话,进展会慢一些,但并非不可能。
She was lucky to have an intelligent and motivated partner in the banking expert. With a more passive source of expertise, she would have made more false starts and depended more on other developers as brainstorming partners. Progress would have been slower, but still possible.
不同领域的专家会根据各自的经验和需求,以不同的视角看待事物。即使是同一个人,经过仔细分析后,也可能提供逻辑上不一致的信息。我们在深入研究项目需求时经常会遇到这类令人头疼的矛盾,而这些矛盾往往是构建更深层次模型的重要线索。有些矛盾仅仅是术语上的差异,或是源于误解。但有些矛盾则可能隐藏在专家们看似相互矛盾的陈述之中。
Different domain experts see things different ways based on their experience and needs. Even the same person provides information that is logically inconsistent after careful analysis. Such pesky contradictions, which we encounter all the time when digging into program requirements, can be great clues to deeper models. Some are just variations in terminology or are based on misunderstanding. But there is a residue where two factual statements by experts seem to contradict.
天文学家伽利略曾提出一个悖论。感官证据清楚地表明地球是静止的:人们并没有被风吹走或落后。然而,哥白尼却提出了一个令人信服的论点,认为地球正以相当快的速度绕太阳运转。调和这两个悖论或许能揭示自然运行的深刻奥秘。
The astronomer Galileo once posed a paradox. The evidence of the senses clearly indicates that the Earth is stationary: people are not being blown off and falling behind. Yet Copernicus had made a compelling argument that the Earth was moving around the sun quite rapidly. Reconciling this might reveal something profound about how nature works.
伽利略设计了一个思想实验。如果骑手从奔跑的马背上扔下一个球,球会落在哪里?当然,球会随着马一起运动,直到落在马蹄附近,就像马静止不动时一样。由此,他推导出惯性参考系概念的雏形,解决了这个悖论,并建立了一个更为实用的运动物理模型。
Galileo devised a thought experiment. If a rider dropped a ball from a running horse, where would it fall? Of course, the ball would move along with the horse until it hit the ground by the horse’s feet, just as if the horse were standing still. From this he deduced an early form of the idea of inertial frames of reference, solving the paradox and leading to a much more useful model of the physics of motion.
好吧。我们遇到的矛盾通常并不那么有趣,其影响也不那么深刻。即便如此,这种思维模式往往有助于我们穿透问题领域的表象,获得更深层次的洞察。
OK. Our contradictions are usually not so interesting, nor the implications so profound. Even so, this same pattern of thought often helps pierce the superficial layers of a problem domain into a deeper insight.
调和所有矛盾既不现实,也可能并非明智之举。(第十四章将深入探讨如何做出决定以及如何应对结果。)然而,即便矛盾依然存在,思考两种说法如何都能适用于同一外部现实,也能带来启发。
It is not practical to reconcile all contradictions, and it may not even be desirable. (Chapter 14 delves into how to decide and how to manage the result.) However, even when a contradiction is left in place, contemplation of how two statements could both apply to the same external reality can be revealing.
在寻找模型概念时,不要忽略显而易见的事物。在许多领域,你可以找到解释基本概念和传统智慧的书籍。你仍然需要与领域专家合作,提炼出与你的问题相关的部分,并将其转化为适合面向对象软件的模型。但你可以从一个连贯且经过深思熟虑的观点开始。
Don’t overlook the obvious when seeking model concepts. In many fields, you can find books that explain the fundamental concepts and conventional wisdom. You still have to work with your own domain experts to distill the part relevant to your problem and to crunch it into something suited to object-oriented software. But you may be able to start with a coherent, deeply considered view.
让我们设想一下之前例子中提到的投资追踪应用程序的另一种场景。和之前一样,故事的开头是开发人员意识到设计变得臃肿不堪,尤其是利息计算器部分。但在这个场景中,领域专家的主要职责在其他方面,他对参与软件开发项目并没有太大兴趣。因此,开发人员无法像之前那样与专家进行头脑风暴,探讨她怀疑隐藏在设计背后的缺失概念。
Let’s imagine a different scenario for the investment-tracking application discussed in the previous example. Just as before, the story starts with the developer realizing that the design is getting unwieldy, particularly the Interest Calculator. But in this scenario, the domain expert’s primary responsibilities lie elsewhere, and he doesn’t have much interest in helping the software development project. In this scenario, the developer couldn’t turn to the expert for a brainstorming session to probe for the missing concepts she suspected to be lurking under the surface.
她没有去买书,而是去了书店。简单浏览了一番后,她找到了一本自己喜欢的会计入门书籍,便快速浏览了一遍。她发现了一整套定义清晰的概念体系。其中有一段话尤其激发了她的思考:
Instead, she went to the bookstore. After a little browsing, she found an introductory accounting book she liked, and she skimmed it. She discovered a whole system of well-defined concepts. An excerpt that particularly fired her thinking:
权责发生制会计。这种方法在收入赚取时确认收入,即使尚未支付。所有费用也都会在发生时确认,无论这些费用是已经支付还是已开具发票待日后支付。任何到期债务,包括税款,都将作为费用列示。
Accrual Basis Accounting. This method recognizes income when it is earned, even if it is not paid. All expenses also show when they are incurred whether they have been paid for or billed to be paid at a later date. Any obligation due, including taxes, will be shown as expense.
— 《财务与会计:如何在没有MBA、CPA或博士学位的情况下记账和管理财务》,作者:苏珊娜·卡普兰(亚当斯传媒,2000年)
—Finance and Accounting: How to Keep Your Books and Manage Your Finances Without an MBA, a CPA or a Ph.D., by Suzanne Caplan (Adams Media, 2000)
这位开发人员不再需要重新发明会计系统。在与另一位开发人员集思广益之后,她想出了一个模型。
The developer no longer needed to reinvent accounting. After some brainstorming with another developer, she came up with a model.
图 9.9. 基于书本学习的更深层次模型
Figure 9.9. A somewhat deeper model based on book learning
她当时没有意识到资产是收入的来源,所以计算器仍然存在。账簿知识仍然保留在应用层,而不是它本应所在的领域层。但她确实将支付问题与收入的应计问题分开了,后者是当时最棘手的问题,并且她将“应计”一词引入了模型和通用语言中。后续迭代可能会对其进行进一步完善。
She did not have the insight that Assets are income generators, and so the Calculators are still there. The knowledge of ledgers is still in the application, rather than the domain layer where it probably belongs. But she did separate the issue of payment from the accrual of income, which was the most problematic area, and she introduced the word “accrual” into the model and into the UBIQUITOUS LANGUAGE. Further refinement could come with later iterations.
当她终于有机会与领域专家交谈时,他感到非常惊讶。这是他第一次遇到一位程序员对他的工作表现出兴趣。由于职责分配的原因,这位专家从未像之前那样与她坐下来一起研究模型。然而,由于这位程序员的知识储备让她能够提出更有针对性的问题,从那以后,专家开始认真倾听她的意见,并尽力及时解答她的问题。
When she did finally have the chance to talk with the domain expert, he was quite surprised. It was the first time a programmer had shown a glimmer of interest in what he did. Due to the way his responsibilities were assigned, the expert never engaged with her, sitting down to go over the model, as happened in the previous scenario. However, because this developer’s knowledge allowed her to ask better questions, from then on the expert did listen to her carefully, and he made a special effort to answer her questions promptly.
当然,这并非非此即彼的问题。即使有领域专家的充分支持,查阅文献以掌握该领域的理论仍然十分有益。大多数企业并没有像会计或财务领域那样精细的模型,但在许多企业中,都曾出现过一些思想家,他们整理并提炼了企业的常见实践。
Of course, this is not an either-or proposition. Even with ample support from domain experts, it pays to look at the literature to get a grasp of the theory of the field. Most businesses do not have models refined to the level of accounting or finance, but in many there have been thinkers in the field who have organized and abstracted the common practices of the business.
开发人员还有另一个选择,那就是阅读其他有开发经验的软件专业人士撰写的文章。在这个领域,例如,《分析模式:可重用对象模型》 (Fowler 1997 )一书的第六章可能会引导她走向一个截然不同的方向,但这并非意味着更好或更差。阅读这类书籍并不会提供现成的解决方案,而是会为她自己的实验提供几个新的起点,以及那些在该领域深耕多年的人的宝贵经验。这样她就无需重复造轮子了。第十一章“应用分析模式”将进一步探讨这一选择。
Yet another option the developer had was to read something written by another software professional with development experience in this domain. For example, Chapter 6 of the book Analysis Patterns: Reusable Object Models (Fowler 1997) would have sent her in quite a different direction, not necessarily better or worse. Such reading would not have provided an off-the-shelf solution. It would have given several new starting points for her own experiments, along with the distilled experience of people who have traveled the territory. She would have been spared reinventing the wheel. Chapter 11, “Applying Analysis Patterns,” will delve further into this option.
我举的例子并不能完全展现其中涉及的反复试验。我可能要尝试六七次不同的谈话思路,才能找到一个清晰且足够有用的方案,以便在模型中尝试。即便如此,我之后至少还会替换一次,因为随着经验的积累和知识的深入,我会想到更好的方案。建模者/设计师不能对自己的想法过于执着。
The examples I’ve given don’t convey the amount of trial and error involved. I might follow half a dozen leads in conversation before finding one that seems clear and useful enough to try out in the model. I’ll end up replacing that one at least once later, as additional experience and knowledge crunching serve up better ideas. A modeler/designer cannot afford to get attached to his own ideas.
所有这些方向的改变并非只是胡乱尝试。每一次改变都为模型注入了更深刻的理解。每一次重构都使设计更加灵活,更容易在下次修改时进行调整,随时准备在需要调整的地方做出相应的改变。
All these changes of direction are not just thrashing. Each change embeds deeper insight into the model. Each refactoring leaves the design more supple, easier to change the next time, ready to bend in the places that turn out to need to bend.
其实别无选择。实验是了解哪些方法有效、哪些无效的唯一途径。试图在设计中避免失误会导致结果质量下降,因为这种做法基于的经验不足。而且,它耗时往往比一系列快速实验更长。
There really is no choice, anyway. Experimentation is the way to learn what works and doesn’t. Trying to avoid missteps in design will result in a lower quality result because it will be based on less experience. And it can easily take longer than a series of quick experiments.
面向对象范式引导我们寻找并创造某些特定类型的概念。事物,即使是像“累积”这样非常抽象的概念,以及这些事物所采取的行动,都是大多数对象模型的核心。这些就是入门级面向对象设计书籍中提到的“名词和动词”。但模型中也可以明确地表达其他重要的概念类别。
The object-oriented paradigm leads us to look for and invent certain kinds of concepts. Things, even very abstract ones such as “accruals,” are the meat of most object models, along with the actions those things take. These are the “nouns and verbs” that introductory object-oriented design books talk about. But other important categories of concepts can be made explicit in a model as well.
我将探讨三种我最初接触物体时并不熟悉的类别。随着我对这些类别的不断学习,我的设计也日臻完善。
I’ll discuss three such categories that were not obvious to me when I started with objects. My designs became sharper with each one of these I learned.
约束条件是模型概念中一个尤为重要的类别。它们通常是隐式产生的,而明确地表达它们可以极大地改进设计。
Constraints make up a particularly important category of model concepts. They often emerge implicitly, and expressing them explicitly can greatly improve a design.
有时,约束条件自然而然地融入到对象或方法中。“桶”对象必须保证其容量不超过其容纳量这一不变性。
Sometimes constraints find a natural home in an object or method. A “Bucket” object must guarantee the invariant that it does not hold more than its capacity.
图 9.10
Figure 9.10
像这样的简单不变式可以通过在每个能够改变内容的操作中使用 case 逻辑来强制执行。
A simple invariant like this can be enforced using case logic in each operation capable of changing contents.
class Bucket {
private float capacity;
private float contents;
public void pourIn(float addedVolume) {
if (contents + addedVolume > capacity) {
contents = capacity;
} else {
contents = contents + addedVolume;
}
}
}
这个逻辑非常简单,规则显而易见。但很容易想象,在一个更复杂的类中,这个限制很容易被忽略。让我们把它提取到一个单独的方法中,并给它起一个清晰明确地表达该限制意义的名字。
This logic is so simple that the rule is obvious. But you can easily imagine this constraint getting lost in a more complicated class. Let’s factor it into a separate method, with a name that clearly and explicitly expresses the significance of the constraint.
class Bucket {
private float capacity;
private float contents;
public void pourIn(float addedVolume) {
float volumePresent = contents + addedVolume;
contents = constrainedToCapacity(volumePresent);
}
private float constrainedToCapacity(float volumePlacedIn) {
if (volumePlacedIn > capacity) return capacity;
return volumePlacedIn;
}
}
这两个版本的代码都强制执行了约束,但第二个版本与模型的关系更为明显(这是模型驱动设计的基本要求)。这条规则本身很简单,很容易理解,但当强制执行的规则变得更加复杂时,它们就会像任何隐式概念一样,开始喧宾夺主,掩盖其所应用的对象或操作。将约束提取到一个单独的方法中,我们可以为其赋予一个能够清晰表达意图的名称,从而使约束在我们的设计中更加明确。现在,它是一个有名称的事物,我们可以对其进行讨论。这种方法也为约束留出了空间。比这更复杂的规则很容易导致方法本身比其调用者(pourIn()在本例中即方法本身)还要长。这样,调用者可以保持简洁,专注于其任务,而约束可以根据需要增加其复杂性。
Both versions of this code enforce the constraint, but the second has a more obvious relationship to the model (the basic requirement of MODEL-DRIVEN DESIGN). This very simple rule was understandable in its original form, but when the rules being enforced are more complex, they start to overwhelm the object or operation they apply to, as any implicit concept does. Factoring the constraint into its own method allows us to give it an intention-revealing name that makes the constraint explicit in our design. It is now a named thing we can discuss. This approach also gives the constraint room. A more complex rule than this might easily produce a method longer than its caller (the pourIn() method, in this case). This way, the caller stays simple and focused on its task while the constraint can grow in complexity if need be.
这种独立的方法为约束条件的扩展留出了一些空间,但在很多情况下,约束条件无法完全融入单个方法中。即使方法本身很简单,它也可能调用对象在其主要职责之外的信息。这条规则可能根本就不适合放在现有的对象中。
This separate method gives the constraint some room to grow, but there are lots of cases when a constraint just can’t fit comfortably in a single method. Or even if the method stays simple, it may call on information that the object doesn’t need for its primary responsibility. The rule may just have no good home in an existing object.
以下是一些警告信号,表明约束正在扭曲其宿主对象的设计。
Here are some warning signs that a constraint is distorting the design of its host object.
1.评估约束需要一些数据,而这些数据并不符合对象的定义。
1. Evaluating a constraint requires data that does not otherwise fit the object’s definition.
2.相关规则出现在多个对象中,强制在原本不属于同一家族的对象之间进行复制或继承。
2. Related rules appear in multiple objects, forcing duplication or inheritance between objects that are not otherwise a family.
3.很多设计和需求讨论都围绕着约束条件展开,但在实现过程中,这些约束条件却被隐藏在过程代码中。
3. A lot of design and requirements conversation revolves around the constraints, but in the implementation, they are hidden away in procedural code.
当约束条件掩盖了对象的基本职责,或者当约束条件在领域中很突出但在模型中却不突出时,您可以将其提取为一个显式对象,甚至可以将其建模为一组对象和关系。(关于此主题的深入、半形式化的论述,请参见《对象约束语言:使用 UML 进行精确建模》 [ Warmer 和 Kleppe,1999 ]。)
When the constraints are obscuring the object’s basic responsibility, or when the constraint is prominent in the domain yet not prominent in the model, you can factor it out into an explicit object or even model it as a set of objects and relationships. (One in-depth, semiformal treatment of this subject can be found in The Object Constraint Language: Precise Modeling with UML [Warmer and Kleppe 1999].)
在第一章中,我们探讨了一种常见的航运业务惯例:预订的货物量比运输工具的实际运力多10%。(经验告诉航运公司,这种超额预订可以弥补临时取消订单的情况,因此他们的船只几乎都能满载出航。)
In Chapter 1, we worked with a common shipping business practice: booking 10 percent more cargo than the transports could handle. (Experience has taught shipping firms that this overbooking compensates for last-minute cancellations, so their ships will sail nearly full.)
通过添加一个表示该约束的新类,在图表和代码中明确地规定了航程和货物之间的关联约束。
This constraint on the association between Voyage and Cargo was made explicit, both in the diagrams and in the code, by adding a new class that represented the constraint.
图 9.11. 重构后的模型,使策略更加明确
Figure 9.11. The model refactored to make policy explicit
要查看完整示例中的代码和推理,请参阅第17页。
To review the code and reasoning in the full example, see page 17.
首先,我们得达成共识:我们不希望将流程作为模型的主要组成部分。对象的作用是封装流程,让我们转而思考流程的目标或意图。
Right up front, let’s agree that we do not want to make procedures a prominent aspect of our model. Objects are meant to encapsulate the procedures and let us think about their goals or intentions instead.
我这里指的是领域中存在的、我们需要在模型中表示的过程。当这些过程出现时,往往会导致对象设计显得笨拙。
What I am talking about here are processes that exist in the domain, which we have to represent in the model. When these emerge, they tend to make for awkward object designs.
本章第一个例子描述了一个货物运输路线规划系统。这个路线规划过程具有实际的商业意义。服务(SERVICE)是一种能够明确表达此类过程,同时又能封装极其复杂算法的方式。
The first example in this chapter described a shipping system that routed cargo. This routing process was something with business meaning. A SERVICE is one way of expressing such a process explicitly, while still encapsulating the extremely complex algorithms.
当执行某个过程有多种方法时,另一种方法是将算法本身或其关键部分作为一个独立的对象。这样,过程的选择就变成了这些对象之间的选择,每个对象都代表一种不同的策略。(第 12 章将更详细地探讨策略在该领域的应用。)
When there is more than one way to carry out a process, another approach is to make the algorithm itself, or some key part of it, an object in its own right. The choice between processes becomes a choice between these objects, each of which represents a different STRATEGY. (Chapter 12 will look in more detail at the use of STRATEGIES in the domain.)
区分应该明确说明的过程和应该隐藏的过程的关键很简单:这是领域专家谈论的内容,还是计算机程序机制的一部分?
The key to distinguishing a process that ought to be made explicit from one that should be hidden is simple: Is this something the domain experts talk about, or is it just part of the mechanism of the computer program?
约束和过程是模型概念的两大类,在面向对象语言编程时,人们往往不会立刻想到它们,但一旦我们开始将它们视为模型元素,它们就能真正地完善设计。
Constraints and processes are two broad categories of model concepts that don’t come leaping to mind when programming in an object-oriented language, yet they can really sharpen up a design once we start thinking about them as model elements.
有些有用的概念类别要窄得多。本章最后,我将介绍一个更具体但又很常见的概念。规范提供了一种简洁的方式来表达某些类型的规则,将它们从条件逻辑中分离出来,并在模型中明确地表达出来。
Some useful categories of concepts are much narrower. I’ll round out this chapter with one much more specific, yet quite common. SPECIFICATION provides a concise way of expressing certain kinds of rules, extricating them from conditional logic and making them explicit in the model.
我与 Martin Fowler 合作开发了SPECIFICATION 模式( Evans 和 Fowler,1997)。该概念看似简单,但其应用和实现却十分微妙,因此本节内容较为详尽。第 10 章将对此模式进行更深入的探讨。在阅读完接下来的模式初步解释后,您可以先略读“应用和实现SPECIFICATIONS ”部分,直到您实际尝试应用该模式为止。
I developed SPECIFICATION in collaboration with Martin Fowler (Evans and Fowler 1997). The simplicity of the concept belies the subtlety in application and implementation, so there is a lot of detail in this section. There will be even more discussion in Chapter 10, where the pattern is extended. After reading the initial explanation of the pattern that follows, you may want to skim the “Applying and Implementing SPECIFICATIONS” section, until you are actually attempting to apply the pattern.
在各种应用程序中,都会出现布尔测试方法,它们实际上是一些小规则的一部分。只要规则简单,我们就可以使用测试方法(例如 ` anIterator.hasNext()is` 或 `is` )来处理它们anInvoice.isOverdue()。在`Invoice`类中,`is` 中的代码isOverdue()是一个用于评估规则的算法。例如:
In all kinds of applications, Boolean test methods appear that are really parts of little rules. As long as they are simple, we handle them with testing methods, such as anIterator.hasNext() or anInvoice.isOverdue(). In an Invoice class, the code in isOverdue() is an algorithm that evaluates a rule. For example,
public boolean isOverdue() {
Date currentDate = new Date();
return currentDate.after(dueDate);
}
但并非所有规则都如此简单。在同一个发票类中,另一条规则anInvoice.isDelinquent()可能首先会检查发票是否逾期,但这仅仅是开始。宽限期的政策可能取决于客户账户的状态。一些逾期发票可以发出第二次催款通知,而另一些则需要移交给催收机构。客户的付款历史、公司针对不同产品线的政策……发票作为付款请求的清晰含义很快就会被大量的规则评估代码所掩盖。发票还会对不支持其基本含义的领域类和子系统产生各种依赖关系。
But not all rules are so simple. On the same Invoice class, another rule, anInvoice.isDelinquent() would presumably start with testing if the Invoice is overdue, but that would just be the beginning. A policy on grace periods could depend on the status of the customer’s account. Some delinquent invoices will be ready for a second notice, while others will be ready to be sent to a collection agency. The payment history of the customer, company policy on different product lines . . . the clarity of Invoice as a request for payment will soon be lost in the sheer mass of rule evaluation code. The Invoice will also develop all sorts of dependencies on domain classes and subsystems that do not support that basic meaning.
此时,为了挽救Invoice类,开发人员通常会将规则评估代码重构到应用层(在本例中为账单催收应用程序)。这样一来,规则就完全脱离了领域层,留下了一个死寂的数据对象,无法表达业务模型中固有的规则。这些规则需要保留在领域层,但它们并不适合被评估的对象(在本例中为Invoice)。不仅如此,评估方法中还会充斥着大量的条件代码,使得规则难以阅读。
At this point, in an attempt to save the Invoice class, a developer will often refactor the rule evaluation code into the application layer (in this case, a bill collection application). Now the rules have been separated from the domain layer altogether, leaving behind a dead data object that does not express the rules inherent in the business model. These rules need to stay in the domain layer, but they don’t fit into the object being evaluated (the Invoice in this case). Not only that, but evaluating methods swell with conditional code, which make the rule hard to read.
采用逻辑编程范式的开发者会以不同的方式处理这种情况。这类规则会被表达为谓词。谓词是求值为“真”或“假”的函数。它们可以通过“与”和“或”等运算符组合起来,表达更复杂的规则。借助谓词,我们可以显式地声明规则并将其用于发票。可惜我们身处逻辑范式之中。
Developers working in the logic-programming paradigm would handle this situation differently. Such rules would be expressed as predicates. Predicates are functions that evaluate to “true” or “false” and can be combined using operators such as “AND” and “OR” to express more complex rules. With predicates, we could declare rules explicitly and use them with the Invoice. If only we were in the logic paradigm.
鉴于此,人们尝试用对象来实现逻辑规则。有些尝试非常精妙,有些则略显稚嫩。有些雄心勃勃,有些则较为保守。有些最终取得了成功,有些则被视为失败的实验而被弃之不用。还有一些尝试甚至导致项目失败。有一点很明确:尽管这个想法很有吸引力,但在对象中完全实现逻辑却是一项艰巨的任务。(毕竟,逻辑编程本身就是一个完整的建模和设计范式。)
Seeing this, people have made attempts at implementing logical rules in terms of objects. Some such attempts were very sophisticated, others naive. Some were ambitious, others modest. Some turned out valuable, some were tossed aside as failed experiments. A few attempts were allowed to derail their projects. One thing is clear: As appealing as the idea is, full implementation of logic in objects is a major undertaking. (After all, logic programming is a whole modeling and design paradigm in its own right.)
业务规则通常与任何显而易见的实体或值对象的职责都不相符,而且它们的种类繁多、组合各异,可能会掩盖领域对象的基本含义。但将规则移出领域层会更糟,因为这样领域代码就无法再表达模型了。
Business rules often do not fit the responsibility of any of the obvious ENTITIES or VALUE OBJECTS, and their variety and combinations can overwhelm the basic meaning of the domain object. But moving the rules out of the domain layer is even worse, since the domain code no longer expresses the model.
逻辑编程提供了称为“谓词”的独立、可组合的规则对象的概念,但用对象完整实现这一概念却很繁琐。此外,它过于通用,不如更专业的设计那样能清晰地传达意图。
Logic programming provides the concept of separate, combinable, rule objects called “predicates,” but full implementation of this concept with objects is cumbersome. It is also so general that it doesn’t communicate intent as much as more specialized designs.
幸运的是,我们并不需要完全实现逻辑编程就能获得显著的收益。我们的大部分规则都属于少数特殊情况。我们可以借鉴谓词的概念,创建一些能够求值为布尔值的专用对象。那些过于复杂的测试方法可以巧妙地扩展成独立的对象。这些对象就像一个个小型真值测试,可以提取出来,成为一个单独的值对象。这个新对象可以对另一个对象进行评估,以判断谓词对该对象是否成立。
Fortunately, we don’t really need to fully implement logic programming to get a large benefit. Most of our rules fall into a few special cases. We can borrow the concept of predicates and create specialized objects that evaluate to a Boolean. Those testing methods that get out of hand will neatly expand into objects of their own. They are little truth tests that can be factored out into a separate VALUE OBJECT. This new object can evaluate another object to see if the predicate is true for that object.
图 9.12
Figure 9.12
换句话说,这个新对象就是一个规范。规范对另一个对象的状态施加约束,而这个约束可能存在,也可能不存在。规范有多种用途,但其中最基本的概念是:规范可以测试任何对象是否满足指定的标准。
To put it another way, the new object is a specification. A SPECIFICATION states a constraint on the state of another object, which may or may not be present. It has multiple uses, but one that conveys the most basic concept is that a SPECIFICATION can test any object to see if it satisfies the specified criteria.
所以:
Therefore:
创建用于特定目的的显式谓词型值对象。规范是一种谓词,用于确定对象是否满足某些条件。
Create explicit predicate-like VALUE OBJECTS for specialized purposes. A SPECIFICATION is a predicate that determines if an object does or does not satisfy some criteria.
许多规范都是简单的、特定用途的测试,例如逾期发票示例。如果规则比较复杂,可以扩展该概念,允许将简单的规范组合起来,就像谓词与逻辑运算符组合一样。(此技术将在下一章讨论。)基本模式保持不变,并提供了一条从简单模型到复杂模型的路径。
Many SPECIFICATIONS are simple, special-purpose tests, as in the delinquent invoice example. In cases where the rules are complex, the concept can be extended to allow simple specifications to be combined, just as predicates are combined with logical operators. (This technique will be discussed in the next chapter.) The fundamental pattern stays the same and provides a path from the simpler to more complex models.
逾期发票的情况可以通过规范进行建模,该规范规定了逾期的含义,并且可以评估任何发票并做出决定。
The case of the delinquent invoice can be modeled using a SPECIFICATION that states what it means to be delinquent and that can evaluate any Invoice and make the determination.
图 9.13. 更详细的违规规则作为规范单独列出
Figure 9.13. A more elaborate delinquency rule factored out as a SPECIFICATION
规范将规则保留在领域层。由于规则是一个完整的对象,其设计可以更明确地反映模型。工厂可以使用来自其他来源的信息(例如客户帐户或公司政策数据库)来配置规范。如果从发票直接访问这些来源,则会将对象耦合在一起,而这种耦合与付款请求(发票的基本职责)无关。在本例中,需要创建逾期发票规范,用于评估一些发票,然后将其丢弃,因此直接内置了特定的评估日期——这是一个不错的简化。规范可以以简单直接的方式获得完成其工作所需的信息。
The SPECIFICATION keeps the rule in the domain layer. Because the rule is a full-fledged object, the design can be a more explicit reflection of the model. A FACTORY can configure a SPECIFICATION using information from other sources, such as the customer’s account or the corporate policy database. Providing direct access to these sources from the Invoice would couple the objects in a way that does not relate to the request for payment (the basic responsibility of Invoice). In this case, the Delinquent Invoice Specification was to be created, used to evaluate some Invoices, and then discarded, so a specific evaluation date was built right in—a nice simplification. A SPECIFICATION can be given the information it will need to do its job in a simple, straightforward way.
规范的基本概念非常简单,它有助于我们思考领域建模问题。但是,模型驱动设计需要一个有效的实现方案,该方案也需要体现这一概念。要做到这一点,就需要深入研究如何应用该模式。领域模式不仅仅是 UML 图中的一个巧妙构思;它是解决编程问题的一种方案,它保留了模型驱动设计的核心理念。
The basic concept of SPECIFICATION is very simple and helps us think about a domain modeling problem. But a MODEL-DRIVEN DESIGN requires an effective implementation that also expresses the concept. To pull that off requires digging a little deeper into how the pattern will be applied. A domain pattern is not just a neat idea for a UML diagram; it is a solution to a programming problem that retains a MODEL-DRIVEN DESIGN.
当你恰当地运用某种模式时,你可以借鉴一整套关于如何解决某一类领域建模问题的思路,并受益于多年积累的有效实现经验。接下来的规范部分将详细讨论:它提供了许多功能选项和实现方法。模式并非菜谱,它让你能够从经验出发来开发解决方案,并为你提供一套描述你工作内容的语言。
When you apply a pattern appropriately, you can tap into a whole body of thought about how to approach a class of domain modeling problem, and you can benefit from years of experience in finding effective implementations. There is a lot of detail in the discussion of SPECIFICATION that follows: many options for features and approaches to implementation. A pattern is not a cookbook. It lets you start from a base of experience to develop your solution, and it gives you some language to talk about what you are doing.
初读时,您可以先略读关键概念。之后,当您遇到类似情况时,可以再回过头来,借鉴文中详细讨论的经验,从而找到解决问题的办法。
You may want to skim the key concepts when first reading. Later, when you run into the situation, you can come back and draw on the experience captured in the detailed discussion. Then you can go and figure out a solution to your problem.
规范的价值很大程度上在于它能够统一看似截然不同的应用程序功能。我们可能需要出于以下三个目的中的一个或多个目的来指定对象的状态。
Much of the value of SPECIFICATION is that it unifies application functionality that may seem quite different. We might need to specify the state of an object for one or more of these three purposes.
1.验证对象,看它是否满足某种需求或是否已准备好用于某种用途。
1. To validate an object to see if it fulfills some need or is ready for some purpose
2.从集合中选择对象(例如查询逾期发票)
2. To select an object from a collection (as in the case of querying for overdue invoices)
3.指定创建一个新对象以满足某种需求
3. To specify the creation of a new object to fit some need
这三种用途——验证、选择和按需构建——在概念层面上是相同的。如果没有像“规范”这样的模式,同一条规则可能会以不同的形式出现,甚至出现相互矛盾的形式,从而失去概念上的统一性。应用“规范”模式可以确保使用一致的模型,即使实现方式可能需要有所不同。
These three uses—validation, selection, and building to order—are the same on a conceptual level. Without a pattern such as SPECIFICATION, the same rule may show up in different guises, and possibly contradictory forms. The conceptual unity can be lost. Applying the SPECIFICATION pattern allows a consistent model to be used, even when the implementation may have to diverge.
规范最简单的用途是验证,也是最直接地展示规范概念的用途。
The simplest use of a SPECIFICATION is validation, and it is the use that demonstrates the concept most straightforwardly.
图 9.14. 应用规范进行验证的模型
Figure 9.14. A model applying a SPECIFICATION for validation
class DelinquentInvoiceSpecification extends
InvoiceSpecification {
private Date currentDate;
// An instance is used and discarded on a single date
public DelinquentInvoiceSpecification(Date currentDate) {
this.currentDate = currentDate;
}
public boolean isSatisfiedBy(Invoice candidate) {
int gracePeriod =
candidate.customer().getPaymentGracePeriod();
Date firmDeadline =
DateUtility.addDaysToDate(candidate.dueDate(),
gracePeriod);
return currentDate.after(firmDeadline);
}
}
现在,假设我们需要在销售人员提到有欠款的客户时显示红色标记。我们只需要在客户端类中编写一个方法,类似这样。
Now, suppose we need to display a red flag whenever a salesperson brings up a customer with delinquent bills. We just have to write a method in a client class, something like this.
public boolean accountIsDelinquent(Customer customer) {
Date today = new Date();
Specification delinquentSpec =
new DelinquentInvoiceSpecification(today);
Iterator it = customer.getInvoices().iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (delinquentSpec.isSatisfiedBy(candidate)) return true;
}
return false;
}
验证是对单个对象进行测试,以确定其是否符合某些标准,其目的是为了方便客户端根据测试结果采取行动。另一个常见的需求是基于某些标准从对象集合中选择一个子集。这里同样适用规范的概念,但实现方面的问题有所不同。
Validation tests an individual object to see if it meets some criteria, presumably so that the client can act on the conclusion. Another common need is to select a subset of a collection of objects based on some criteria. The same concept of SPECIFICATION can be applied here, but implementation issues are different.
假设某个应用程序需要列出所有逾期发票的客户。理论上,我们之前定义的逾期发票规范仍然适用,但实际上其实现可能需要更改。为了说明概念相同,我们首先假设发票数量很少,可能已经加载到内存中。在这种情况下,之前为验证而开发的简单实现仍然有效。发票库可以提供一种通用的方法,根据规范选择发票:
Suppose there was an application requirement to list all customers with delinquent Invoices. In theory, the Delinquent Invoice Specification that we defined before will still serve, but in practice its implementation would probably have to change. To demonstrate that the concept is the same, let’s assume first that the number of Invoices is small, maybe already in memory. In this case, the straightforward implementation developed for validation still serves. The Invoice Repository could have a generalized method to select Invoices based on a SPECIFICATION:
public Set selectSatisfying(InvoiceSpecification spec) {
Set results = new HashSet();
Iterator it = invoices.iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (spec.isSatisfiedBy(candidate)) results.add(candidate);
}
return results;
}
因此,客户只需提交一份代码声明即可获取所有逾期发票的汇总信息:
So a client could obtain a collection of all delinquent Invoices with a single code statement:
Set delinquentInvoices = invoiceRepository.selectSatisfying(
new DelinquentInvoiceSpecification(currentDate));
那一行代码确立了操作背后的概念。当然,发票对象可能并不在内存中。它们的数量可能成千上万。在典型的业务系统中,数据很可能存储在关系数据库中。而且,正如前面章节所指出的,在与其他技术的交叉领域,模型的核心往往会被忽略。
That line of code establishes the concept behind the operation. Of course, the Invoice objects probably aren’t in memory. There may be thousands of them. In a typical business system, the data is probably in a relational database. And, as pointed out in earlier chapters, the model focus tends to get lost at these intersections with other technologies.
关系型数据库拥有强大的搜索功能。我们如何才能在保持规范模型本质的同时,高效地利用这种功能来解决这个问题呢?模型驱动设计要求模型与实现保持同步,但它也允许我们自由选择任何能够忠实地捕捉模型含义的实现方式。幸运的是,SQL 是一种非常自然的编写规范的方式。
Relational databases have powerful search capabilities. How can we take advantage of that power to solve this problem efficiently while retaining the model of a SPECIFICATION? MODEL-DRIVEN DESIGN demands that the model stay in lockstep with the implementation, but it allows freedom to choose any implementation that faithfully captures the meaning of the model. Lucky for us, SQL is a very natural way to write SPECIFICATIONS.
以下是一个简单的示例,其中查询与验证规则封装在同一个类中。发票规范中添加了一个方法,并在逾期发票规范子类中实现:
Here is a simple example, in which the query is encapsulated in the same class as the validation rule. A single method is added to the Invoice Specification and is implemented in the Delinquent Invoice Specification subclass:
public String asSQL() {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(currentDate);
}
规范与存储库无缝衔接,存储库是提供对领域对象的查询访问并封装数据库接口的构建块机制(见图9.15)。
SPECIFICATIONS mesh smoothly with REPOSITORIES, which are the building-block mechanisms for providing query access to domain objects and encapsulating the interface to the database (see Figure 9.15).
图 9.15.存储库与规范之间的交互
Figure 9.15. The interaction between REPOSITORY and SPECIFICATION
这种设计存在一些问题。最重要的是,表结构的细节泄露到了领域层;它们应该被隔离在一个映射层中,该映射层负责将领域对象与关系表关联起来。在这里隐式地复制这些信息可能会损害发票和客户对象的可修改性和可维护性,因为现在对它们映射的任何更改都需要在多个地方进行跟踪。但这个例子只是简单地说明了如何将规则保留在一个地方。一些对象关系映射框架提供了用模型对象和属性来表达此类查询的方法,并在基础架构层生成实际的 SQL。这样我们就能两全其美了。
Now this design has some problems. Most important, the details of the table structure have leaked into the DOMAIN LAYER; they should be isolated in a mapping layer that relates the domain objects to the relational tables. Implicitly duplicating that information here could hurt the modifiability and maintainability of the Invoice and Customer objects, because any change to their mappings now have to be tracked in more than one place. But this example is a simple illustration of how to keep the rule in just one place. Some object-relational mapping frameworks provide the means to express such a query in terms of the model objects and attributes, generating the actual SQL in the infrastructure layer. This would let us have our cake and eat it too.
当基础设施无法提供帮助时,我们可以通过向发票存储库添加专门的查询方法来重构 SQL,使其不再依赖于表达力强的领域对象。为了避免将规则嵌入到存储库中,我们必须以更通用的方式表达查询,这种方式不会捕获规则,但可以组合或置于上下文中来推导出规则(在本例中,通过使用双重分派)。
When the infrastructure doesn’t come to the rescue, we can refactor the SQL out of the expressive domain objects by adding a specialized query method to the Invoice Repository. To avoid embedding the rule into the REPOSITORY, we have to express the query in a more generic way, one that doesn’t capture the rule but can be combined or placed in context to work the rule out (in this example, by using a double dispatch).
public class InvoiceRepository {
public Set selectWhereGracePeriodPast(Date aDate){
//This is not a rule, just a specialized query
String sql = whereGracePeriodPast_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}
public String whereGracePeriodPast_SQL(Date aDate) {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(aDate);
}
public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}
发票规范asSql()中的方法被替换为,逾期发票规范实现如下:satisfyingElementsFrom(InvoiceRepository)
The asSql() method on Invoice Specification is replaced with satisfyingElementsFrom(InvoiceRepository), which Delinquent Invoice Specification implements as:
public class DelinquentInvoiceSpecification {
// Basic DelinquentInvoiceSpecification code here
public Set satisfyingElementsFrom(
InvoiceRepository repository) {
//Delinquency rule is defined as:
// "grace period past as of current date"
return repository.selectWhereGracePeriodPast(currentDate);
}
}
这会将 SQL 代码放入 REPOSITORY 中,而SPECIFICATION则控制应该使用哪个查询。规则并没有像 REPOSITORY 那样整齐地组织在SPECIFICATION中,但它包含了构成违规(即超过宽限期)的基本声明。
This puts the SQL in the REPOSITORY, while the SPECIFICATION controls what query should be used. The rules aren’t as neatly collected into the SPECIFICATION, but the essential declaration is there of what constitutes delinquency (that is, past grace period).
REPOSITORY现在有一个非常特殊的查询,很可能只会在这种情况下使用。这可以接受,但是,根据逾期发票与拖欠发票的相对数量,一个中间方案,即保持REPOSITORY方法更通用,可能仍然能够提供良好的性能,同时使SPECIFICATION更易于理解。
The REPOSITORY now has a very specialized query that most likely will be used only in this case. That is acceptable, but depending on the relative numbers of Invoices that are overdue compared to those that are delinquent, an intermediate solution that leaves the REPOSITORY methods more generic may still give good performance, while keeping the SPECIFICATION more self-explanatory.
public class InvoiceRepository {
public Set selectWhereDueDateIsBefore(Date aDate) {
String sql = whereDueDateIsBefore_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}
public String whereDueDateIsBefore_SQL(Date aDate) {
return
"SELECT * FROM INVOICE" +
" WHERE INVOICE.DUE_DATE" +
" < " + SQLUtility.dateAsSQL(aDate);
}
public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}
public class DelinquentInvoiceSpecification {
//Basic DelinquentInvoiceSpecification code here
public Set satisfyingElementsFrom(
InvoiceRepository repository) {
Collection pastDueInvoices =
repository.selectWhereDueDateIsBefore(currentDate);
Set delinquentInvoices = new HashSet();
Iterator it = pastDueInvoices.iterator();
while (it.hasNext()) {
Invoice anInvoice = (Invoice) it.next();
if (this.isSatisfiedBy(anInvoice))
delinquentInvoices.add(anInvoice);
}
return delinquentInvoices;
}
}
这段代码会造成性能损失,因为我们需要提取更多发票,然后从内存中进行选择。这种性能损失是否能换来更好的责任划分,完全取决于具体情况。有很多方法可以实现规范和存储库之间的交互,从而充分利用开发平台,同时保持基本职责不变。
We’ll take a performance hit with this code, because we pull out more Invoices and then have to select from them in memory. Whether this is an acceptable cost for the better factoring of responsibility depends entirely on circumstances. There are many ways to implement the interactions between SPECIFICATIONS and REPOSITORIES, to take advantage of the development platform, while keeping the basic responsibilities in place.
有时,为了提高性能,或者更可能是为了加强安全性,查询可能会在服务器端以存储过程的形式实现。在这种情况下,SPECIFICATION可以只包含存储过程允许的参数。尽管如此,这些不同的实现方式在模型上并没有区别。除了模型明确规定的限制外,实现方式的选择是自由的。但代价是编写和维护查询的方式会更加繁琐。
Sometimes, to improve performance, or more likely to tighten security, queries may be implemented on the server as stored procedures. In that case, the SPECIFICATION could carry only the parameters allowed by the stored procedure. For all that, there is no difference in the model between these various implementations. The choice of implementation is free except where specifically constrained by the model. The price comes in a more cumbersome way of writing and maintaining queries.
本次讨论仅仅触及了将规范与数据库相结合所面临挑战的皮毛,我也不打算涵盖所有可能出现的考量因素。我只想让大家初步了解一下需要做出的选择。Mee 和 Hieatt 在 Fowler 2003 年的著作中讨论了使用规范设计存储库时涉及的一些技术问题。
This discussion barely scratches the surface of the challenges of combining SPECIFICATIONS with databases, and I’ll make no attempt to cover all the considerations that may arise. I just want to give a taste of the kind of choices that have to be made. Mee and Hieatt discuss a few of the technical issues involved in designing REPOSITORIES with SPECIFICATIONS in Fowler 2003.
当五角大楼需要新型战斗机时,官员们会制定一份技术规格说明。这份规格说明可能要求战斗机达到2马赫的速度,航程1800英里,造价不超过5000万美元等等。但无论规格说明多么详细,它都不是飞机的设计方案,更不是飞机本身。航空航天工程公司会根据这份规格说明,设计出一个或多个方案。不同的公司可能会提出不同的方案,但所有这些方案都必须满足最初的规格说明。
When the Pentagon wants a new fighter jet, officials write a specification. This specification may require that the jet reach Mach 2, that it have a range of 1800 miles, that it cost no more than $50 million, and so on. But however detailed it is, the specification is not a design for a plane, much less a plane. An aerospace engineering company will take the specification and create one or more designs based on it. Competing companies may produce different designs, all of which presumably satisfy the original spec.
许多计算机程序会生成各种内容,而这些内容都需要预先指定。例如,当您在文字处理文档中插入图片时,文本会围绕图片自动排列。您需要指定图片的位置,或许还要指定文本的排列方式。文字处理程序会根据您的指定,自动计算出页面上文字的确切位置。
Many computer programs generate things, and those things have to be specified. When you place a picture into a word-processing document, the text flows around it. You have specified the location of the picture, and perhaps the style of text flow. The exact placement of the words on the page is then worked out by the word processor in such a way that it meets your specification.
虽然乍看之下可能并不明显,但这与验证和选择中使用的“规范”概念相同。我们正在为尚未存在的对象指定标准。然而,具体的实现方式却截然不同。与查询不同,此规范并非对已存在对象进行筛选;也与验证不同,它并非对已存在对象进行测试。这一次,我们将创建或重新配置一个全新的对象或一组对象,以满足规范的要求。
Although it may not be apparent at first, this is the same concept of a SPECIFICATION that was applied to validation and selection. We are specifying criteria for objects that are not yet present. The implementation will be quite different, however. This SPECIFICATION is not a filter for preexisting objects, as with querying. It is not a test for an existing object, as with validation. This time, a whole new object or set of objects will be made or reconfigured to satisfy the SPECIFICATION.
无需使用规范,也可以编写一个生成器,其中包含创建所需对象的过程或指令集。这段代码隐式地定义了生成器的行为。
Without using SPECIFICATION, a generator can be written that has procedures or a set of instructions that create the needed objects. This code implicitly defines the behavior of the generator.
相反,生成器的接口通过描述性的规范(SPECIFICATION)进行定义,从而明确地约束生成器的产物。这种方法有几个优点。
Instead, an interface of the generator that is defined in terms of a descriptive SPECIFICATION explicitly constrains the generator’s products. This approach has several advantages.
•生成器的实现与其接口解耦。规范声明了输出的要求,但没有定义如何获得该结果。
• The generator’s implementation is decoupled from its interface. The SPECIFICATION declares the requirements for the output but does not define how that result is reached.
• 该接口明确地传达了其规则,因此开发人员无需了解生成器的所有操作细节即可知道其预期结果。预测过程式生成器的行为的唯一方法是运行用例或理解每一行代码。
• The interface communicates its rules explicitly, so developers can know what to expect from the generator without understanding all details of its operation. The only way to predict the behavior of a procedurally defined generator is to run cases or to understand every line of code.
• 由于请求的陈述掌握在客户手中,而生成器只有义务满足规范中的字面意思,因此该界面更加灵活,或者可以通过增加灵活性来增强。
• The interface is more flexible, or can be enhanced with more flexibility, because the statement of the request is in the hands of the client, while the generator is only obligated to fulfill the letter of the SPECIFICATION.
最后,这种接口更容易测试,因为模型包含一种明确定义生成器输入的方法,该方法同时也是对输出的验证。也就是说,传递给生成器接口以约束创建过程的同一个规范(SPECIFICATION)也可以用作验证角色(如果实现支持),以确认创建的对象是否正确。(这是第 10 章讨论的断言(ASSERTION)的示例。)
• Last, but not least, this kind of interface is easier to test, because the model contains an explicit way to define input into the generator that is also a validation of the output. That is, the same SPECIFICATION that is passed into the generator’s interface to constrain the creation process can also be used, in its validation role (if the implementation supports it) to confirm that the created object is correct. (This is an example of an ASSERTION, discussed in Chapter 10.)
按订单生产可能意味着从头开始创建一个对象,但也可能是对现有对象进行配置以满足规范。
Building to order can mean creation of an object from scratch, but it can also be a configuration of preexisting objects to satisfy the SPEC.
仓库里堆放着各种化学品,装在类似货车车厢的大型容器中。有些化学品性质稳定,几乎可以存放在任何地方;有些易挥发,必须存放在特制的通风容器中;有些易爆,必须存放在特制的装甲容器中。此外,对于容器内允许存放的化学品组合也有相应的规定。
There is a warehouse in which various chemicals are stored in stacks of large containers, similar to boxcars. Some chemicals are inert and can be stored just about anywhere. Some are volatile and have to be stored in specially ventilated containers. Some are explosive and have to be stored in specially armored containers. There are also rules about the combinations allowed in a container.
目标是编写软件,找到一种高效、安全的方法将化学品放入容器中。
The goal is to write software that will find an efficient and safe way to put the chemicals in the containers.
图 9.16. 仓库存储模型
Figure 9.16. A model for warehouse storage
我们可以先编写一个程序,将化学物质放入容器中,但我们不妨先从验证问题入手。这将迫使我们明确规则,并为最终实现提供测试方法。
We could start by writing a procedure to take a chemical and place it in a container, but instead, let’s start with the validation problem. This will force us to make the rules explicit, and it will give us a way to test the final implementation.
每种化学品都将有其容器规格:
Each chemical will have a container SPECIFICATION:
现在,如果我们把这些写成容器规范,我们就能够获取打包容器的配置,并测试它是否满足这些约束。
Now, if we write these as Container Specifications, we should be able to take a configuration of packed containers and test to see if it meets these constraints.
需要在容器规范中实现一种方法,以检查所需的容器特性。例如,附加到爆炸性化学品的规范将查找“装甲”特性:isSatisfied()
A method on Container Specification, isSatisfied(), would have to be implemented to check for needed ContainerFeatures. For example, the SPEC attached to an explosive chemical would look for the “armored” feature:
public class ContainerSpecification {
private ContainerFeature requiredFeature;
public ContainerSpecification(ContainerFeature required) {
requiredFeature = required;
}
boolean isSatisfiedBy(Container aContainer){
return aContainer.getFeatures().contains(requiredFeature);
}
}
以下是设置爆炸性化学品的示例客户端代码:
Here is sample client code to set up an explosive chemical:
tnt.setContainerSpecification(
new ContainerSpecification(ARMORED));
容器对象的一个方法,isSafelyPacked(),将确认容器具有其所包含化学品的所有指定特性:
A method on a Container object, isSafelyPacked(), will confirm that Container has all the features specified by the Chemicals it contains:
boolean isSafelyPacked(){
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
if (!drum.containerSpecification().isSatisfiedBy(this))
return false;
}
return true;
}
此时,我们可以编写一个监控应用程序,该程序可以获取库存数据库并报告任何不安全的情况。
At this point, we could write a monitoring application that would take the inventory database and report any unsafe situations.
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (!container.isSafelyPacked())
unsafeContainers.add(container);
}
这并非我们被要求编写的软件。虽然应该让业务人员了解这个机会,但我们目前的任务是设计一个包装器。我们现在有一个包装器的测试用例。基于对领域的理解以及我们基于规范的模型,我们能够为一项服务定义一个清晰简洁的接口,该服务将接收桶和容器的集合,并按照规则进行包装。
This is not the software we’ve been asked to write. It would be good to let the business people know about the opportunity, but we have been charged with designing a packer. What we have is a test for a packer. This understanding of the domain and our SPECIFICATION-based model put us in a position to define a clear and simple interface for a SERVICE that will take collections of Drums and Containers and pack them in compliance with the rules.
public interface WarehousePacker {
public void pack(Collection containersToFill,
Collection drumsToPack) throws NoAnswerFoundException;
/* ASSERTION: At end of pack(), the ContainerSpecification
of each Drum shall be satisfied by its Container.
If no complete solution can be found, an exception shall
be thrown. */
}
现在,设计一个能够履行Packer服务职责的优化约束求解器的任务已经从应用程序的其余部分解耦出来,这些机制不会使表达模型的设计部分变得臃肿。(参见第 10 章“声明式设计风格”和第 15 章“内聚机制”。)然而,控制打包的规则并没有从领域对象中分离出来。
Now the task of designing an optimized constraint solver to fulfill the responsibilities of the Packer service has been decoupled from the rest of the application, and those mechanisms will not clutter the part of the design that expresses the model. (See “Declarative Style of Design,” Chapter 10, and COHESIVE MECHANISM, Chapter 15.) Yet the rules governing packing have not been pulled out of the domain objects.
编写仓库打包软件的优化逻辑是一项艰巨的任务。一个由开发人员和业务专家组成的小团队已经分头着手这项工作,但他们甚至还没开始编写代码。与此同时,另一个小团队正在开发一个应用程序,该程序允许用户从数据库中提取库存信息,将其导入打包系统并解读结果。他们正试图为预期的打包系统进行设计。但他们目前只能制作一个用户界面原型并编写一些数据库集成代码。他们无法向用户展示具有实际意义的界面,从而获得有效的反馈。出于同样的原因,打包系统团队的工作也处于孤立状态。
Writing the optimization logic to make the warehouse packing software work is a big job. A small team of developers and business experts have split off and have set to work on it, but they haven’t even begun to code. Meanwhile, another small team is developing the application that will allow users to pull inventory from the database, feed it to the Packer, and interpret the results. They are trying to design for the anticipated Packer. But all they can do is mock up a UI and work on some database integration code. They can’t show the users an interface with meaningful behavior to get good feedback. For the same reason, the Packer team is working in a vacuum too.
借助仓库打包器示例中创建的领域对象和SERVICE接口,应用程序团队意识到他们可以构建一个非常简单的打包器实现,这有助于推进开发过程,使工作能够并行进行并闭合反馈回路,而这只有在一个可运行的端到端系统中才能充分发挥作用。
With the domain objects and SERVICE interface made in the warehouse packer example, the application team realizes they could build a very simple implementation of a Packer that could help the development process move along, allowing work to go forward in parallel and closing the feedback loop, which only reaches full effect with a working end-to-end system.
public class Container {
private double capacity;
private Set contents; //Drums
public boolean hasSpaceFor(Drum aDrum) {
return remainingSpace() >= aDrum.getSize();
}
public double remainingSpace() {
double totalContentSize = 0.0;
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum aDrum = (Drum) it.next();
totalContentSize = totalContentSize + aDrum.getSize();
}
return capacity – totalContentSize;
}
public boolean canAccommodate(Drum aDrum) {
return hasSpaceFor(aDrum) &&
aDrum.getContainerSpecification().isSatisfiedBy(this);
}
}
public class PrototypePacker implements WarehousePacker {
public void pack(Collection containers, Collection drums)
throws NoAnswerFoundException {
/* This method fulfills the ASSERTION as written. However,
when an exception is thrown, Containers' contents may
have changed. Rollback must be handled at a higher
level. */
Iterator it = drums.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
Container container =
findContainerFor(containers, drum);
container.add(drum);
}
}
public Container findContainerFor(
Collection containers, Drum drum)
throws NoAnswerFoundException {
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (container.canAccommodate(drum))
return container;
}
throw new NoAnswerFoundException();
}
}
诚然,这段代码还有很多不足之处。它可能会先把沙子装进专用容器,然后在装危险化学品之前就因为空间不足而无法完成。它当然也无法实现收益最大化。但很多优化问题本来就很难完美解决。而且,这个实现方式确实遵循了目前为止所述的规则。
Granted that this code leaves a lot to be desired. It might pack sand into specialty containers and then run out of room before it packs the hazardous chemicals. It certainly doesn’t optimize revenues. But a lot of optimization problems are never solved perfectly anyway. This implementation does follow the rules that have been stated so far.
有了这个原型,应用程序开发人员就可以全速推进项目,包括与外部系统的所有集成。Packer开发团队还能获得领域专家的反馈,他们与原型互动并完善想法,从而帮助明确需求和优先级。Packer团队决定接手原型并进行调整,以测试各种想法。
Having this prototype lets the application developers move at full speed, including all integrations with external systems. The Packer development team also gets feedback as domain experts interact with the prototype and firm up their ideas, helping clarify requirements and priorities. The Packer team decides to take over the prototype and tweak it to test ideas.
他们还会不断更新界面,使其与最新设计保持一致,从而迫使对应用程序和一些领域对象进行重构,进而及早解决集成问题。
They also keep the interface up-to-date with their latest design, forcing refactoring of the application, and some domain objects, thereby tackling the integration problems early.
一旦功能强大的Packer准备就绪,集成就变得轻而易举,因为它是按照一个特征明确的接口编写的——与应用程序在与原型交互时所使用的接口和断言相同。
As soon as the sophisticated Packer is ready, integration is a breeze because it has been written to a well-characterized interface—the same interface and ASSERTIONS that the application was written for when interacting with the prototype.
优化算法专家们花了数月时间才最终完成这项工作。他们受益于用户与原型互动所获得的反馈。与此同时,系统的其他所有部分在开发过程中都有可供交互的对象。
It took specialists in optimization algorithms months to get it right. They benefited from the feedback they could get from users interacting with the prototype. In the meantime, all other parts of the system have something to interact with during development.
这里我们看到一个例子,说明一个“最简单的可行方案”是如何通过更复杂的模型实现的。我们可以拥有一个非常复杂的功能原型。只需几十行易于理解的代码即可实现该组件。如果采用非模型驱动的方法,则更难理解,更难升级(因为Packer与设计的其他部分耦合性更强),而且在这种情况下,原型制作可能需要更长时间。
Here we have an example of a “simplest thing that could possibly work” that actually becomes possible because of a more sophisticated model. We can have a functioning prototype of a very complex component in a couple dozen lines of easily understood code. A less MODEL-DRIVEN approach would be harder to understand, would be harder to upgrade (because the Packer would be more coupled to the rest of the design), and in this case, would likely take longer to prototype.
软件的最终目的是服务用户。但首先,软件必须服务于开发者。在强调重构的开发过程中,这一点尤为重要。随着程序的演进,开发者会重新组织和重写每个部分。他们会将领域对象集成到应用程序中,并添加新的领域对象。即使多年以后,维护程序员仍然需要修改和扩展代码。人们必须处理这些代码。但他们真的愿意吗?
The ultimate purpose of software is to serve users. But first, that same software has to serve developers. This is especially true in a process that emphasizes refactoring. As a program evolves, developers will rearrange and rewrite every part. They will integrate the domain objects into the application and with new domain objects. Even years later, maintenance programmers will be changing and extending the code. People have to work with this stuff. But will they want to?
当具有复杂行为的软件缺乏良好的设计时,重构或合并元素就会变得困难。一旦开发人员无法准确预测计算的全部后果,重复代码就会开始出现。当设计元素是单体式的,无法重新组合时,重复代码就不可避免了。类和方法可以拆分以提高重用性,但这会变得很困难。要时刻关注所有小部件的功能。如果软件设计混乱,开发人员甚至不敢查看现有的混乱代码,更别提进行任何可能加剧混乱或因意外依赖关系而导致功能崩溃的更改了。除了极小的系统之外,这种脆弱性限制了可构建的功能丰富度,并阻碍了重构和迭代改进。
When software with complex behavior lacks a good design, it becomes hard to refactor or combine elements. Duplication starts to appear as soon as a developer isn’t confident of predicting the full implications of a computation. Duplication is forced when design elements are monolithic, so that the parts cannot be recombined. Classes and methods can be broken down for better reuse, but it gets hard to keep track of what all the little parts do. When software doesn’t have a clean design, developers dread even looking at the existing mess, much less making a change that could aggravate the tangle or break something through an unforeseen dependency. In any but the smallest systems, this fragility places a ceiling on the richness of behavior it is feasible to build. It stops refactoring and iterative refinement.
要让项目随着开发进程加速前进,而不是被自身的历史包袱所拖累,就需要一个易于使用、乐于变革的设计。一个灵活的设计。
To have a project accelerate as development proceeds—rather than get weighed down by its own legacy—demands a design that is a pleasure to work with, inviting to change. A supple design.
灵活的设计是对深度建模的补充。一旦你挖掘出隐含的概念并将其显式化,你就拥有了原材料。通过迭代循环,你可以将这些原材料打磨成有用的形状,构建一个能够简洁清晰地捕捉关键问题的模型,并塑造一个能够让客户开发人员真正运用该模型的设计。设计和代码的开发会带来新的洞察,从而完善模型概念。如此循环往复——我们又回到了迭代循环,并通过重构来获得更深层次的洞察。但是,你最终想要实现什么样的设计?在这个过程中,你应该尝试哪些实验?这就是本章要探讨的内容。
Supple design is the complement to deep modeling. Once you’ve dug out implicit concepts and made them explicit, you have the raw material. Through the iterative cycle, you hammer that material into a useful shape, cultivating a model that simply and clearly captures the key concerns, and shaping a design that allows a client developer to really put that model to work. Development of the design and code leads to insight that refines model concepts. Round and round—we’re back to the iterative cycle and refactoring toward deeper insight. But what kind of design are you trying to arrive at? What kind of experiments should you try along the way? That is what this chapter is about.
许多过度设计都被冠以“灵活性”之名。但通常情况下,过多的抽象层和间接性反而会成为阻碍。看看那些真正能赋能使用者的软件设计,你会发现它们通常都很简洁。简洁并不意味着容易。要创造出能够组装成复杂系统且易于理解的元素,必须将模型驱动设计与适度严谨的设计风格相结合。创建或使用这样的系统可能需要相当高超的设计技能。
A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way. Look at the design of software that really empowers the people who handle it; you will usually see something simple. Simple is not easy. To create elements that can be assembled into elaborate systems and still be understandable, a dedication to MODEL-DRIVEN DESIGN has to be joined with a moderately rigorous design style. It may well require relatively sophisticated design skill to create or to use.
开发人员扮演两种角色,设计必须同时满足这两种角色的需求。同一个人完全可以同时扮演这两种角色——甚至可以在几分钟内切换——但他们与代码的关系却截然不同。一种角色是客户端开发人员,他们将领域对象融入应用程序代码或其他领域层代码中,并充分利用设计的功能。灵活的设计揭示了深层的底层模型,使其潜力清晰可见。客户端开发人员可以灵活地使用一组精简且松散耦合的概念来表达领域中的各种场景。设计元素以自然的方式相互契合,最终得到可预测、特征清晰且稳健的结果。
Developers play two roles, each of which must be served by the design. The same person might well play both roles—even switch back and forth in minutes—but the relationship to the code is different nonetheless. One role is the developer of a client, who weaves the domain objects into the application code or other domain layer code, utilizing capabilities of the design. A supple design reveals a deep underlying model that makes its potential clear. The client developer can flexibly use a minimal set of loosely coupled concepts to express a range of scenarios in the domain. Design elements fit together in a natural way with a result that is predictable, clearly characterized, and robust.
同样重要的是,设计必须服务于负责修改它的开发人员。为了便于修改,设计必须易于理解,并清晰地展现出客户端开发人员所依据的底层模型。它必须遵循领域深层模型的轮廓,因此大多数更改都会在设计中灵活调整。代码的影响必须清晰透明,以便能够轻松预测更改的后果。
Equally important, the design must serve the developer working to change it. To be open to change, a design must be easy to understand, revealing that same underlying model that the client developer is drawing on. It must follow the contours of a deep model of the domain, so most changes bend the design at flexible points. The effects of its code must be transparently obvious, so the consequences of a change will be easy to anticipate.
早期设计版本通常比较僵硬。很多设计在项目的时间和预算范围内都无法变得灵活。我从未见过哪个大型项目从头到尾都保持这种特性。但是,当复杂性阻碍项目进展时,将最关键、最复杂的部分打磨成灵活的设计,就能决定你是会陷入遗留系统的维护泥潭,还是能够突破复杂性的瓶颈。
Early versions of a design are usually stiff. Many never acquire any suppleness in the time frame or budget of the project. I’ve never seen a large program that had this quality throughout. But when complexity is holding back progress, honing the most crucial, intricate parts to a supple design makes the difference between getting sucked down into legacy maintenance and punching through the complexity ceiling.
设计这类软件并没有固定的模式,但我总结了一些经验,这些模式如果运用得当,往往能赋予设计灵活性。这些模式和示例应该能让你感受到灵活设计的特点以及背后的思考方式。
There is no formula for designing software like this, but I have culled a set of patterns that, in my experience, tend to lend suppleness to a design when they fit. These patterns and examples should give a feel for what a supple design is like and the kind of thinking that goes into it.
图 10.1. 一些有助于柔性设计的模式
Figure 10.1. Some patterns that contribute to supple design
在领域驱动设计中,我们需要思考有意义的领域逻辑。如果代码只是执行规则却没有明确说明规则,就会迫使我们思考软件的逐步执行过程。同样的道理也适用于那些仅仅通过运行代码而得出的计算结果,但这些计算过程本身并没有明确说明。如果没有与模型建立清晰的联系,就很难理解代码的效果,也很难预测更改后的结果。上一章深入探讨了如何显式地建模规则和计算。实现这类对象需要对计算的细节或规则的细则有深入的理解。对象的优势在于它们能够封装所有这些细节,从而使客户端代码简洁明了,并能用更高层次的概念来解释。
In domain-driven design, we want to think about meaningful domain logic. Code that produces the effect of a rule without explicitly stating the rule forces us to think of step-by-step software procedures. The same applies to a calculation that just results from running some code, but isn’t explicit. Without a clear connection to the model, it is difficult to understand the effect of the code or anticipate the effect of a change. The previous chapter delved into modeling rules and calculations explicitly. Implementing such objects requires a lot of understanding of the gritty details of the calculation or the fine print of the rule. The beauty of objects is their ability to encapsulate all that, so that client code is simple and can be interpreted in terms of higher-level concepts.
但如果接口没有告诉客户端开发者有效使用对象所需的信息,他最终还是得深入研究内部细节。客户端代码的阅读者也一样。这样一来,封装的大部分价值就丧失了。我们始终面临着认知过载的问题:如果客户端开发者的脑海中充斥着组件如何运作的细节,他就无法清晰地理解客户端设计的复杂性。即使是同一个人同时扮演开发和使用自己代码的角色,情况也是如此,因为即使他不必了解那些细节,他同时能够考虑的因素也是有限的。
But if the interface doesn’t tell the client developer what he needs to know in order to use the object effectively, he will have to dig into the internals to understand the details anyway. A reader of the client code will have to do the same. Then most of the value of the encapsulation is lost. We are always fighting cognitive overload: If the client developer’s mind is flooded with detail about how a component does its job, his mind isn’t clear to work out the intricacies of the client design. This is true even when the same person is playing both roles, developing and using his own code, because even if he doesn’t have to learn those details, there is a limit to how many factors he can consider at once.
如果开发者必须考虑组件的具体实现才能使用它,那么封装的价值就丧失了。如果除原开发者之外的其他人必须根据对象的实现来推断其用途,那么这位新开发者很可能只是偶然地推断出该操作或类所实现的用途。如果这并非开发者的本意,那么代码或许暂时可以运行,但设计的概念基础已被破坏,两位开发者也将各行其是。
If a developer must consider the implementation of a component in order to use it, the value of encapsulation is lost. If someone other than the original developer must infer the purpose of an object or operation based on its implementation, that new developer may infer a purpose that the operation or class fulfills only by chance. If that was not the intent, the code may work for the moment, but the conceptual basis of the design will have been corrupted, and the two developers will be working at cross-purposes.
为了充分发挥以类或方法形式显式建模概念的价值,我们必须为这些程序元素赋予能够反映这些概念的名称。类和方法的名称是改善开发人员之间沟通以及提升系统抽象程度的绝佳途径。
To obtain the value of explicitly modeling a concept in the form of a class or method, we must give these program elements names that reflect those concepts. The names of classes and methods are great opportunities for improving communication between developers, and for improving the abstraction of the system.
Kent Beck曾撰文指出,可以通过“意图揭示选择器”( INTENTION-REVEALING SELECTOR )来使方法名称传达其用途(Beck 1997)。设计中的所有公共元素共同构成其接口,而每个元素的名称都提供了一个揭示设计意图的机会。类型名称、方法名称和参数名称共同构成了一个“意图揭示接口”(INTENTION-REVEALING INTERFACE)。
Kent Beck wrote of making method names communicate their purpose with an INTENTION-REVEALING SELECTOR (Beck 1997). All public elements of a design together make up its interface, and the name of each of those elements presents an opportunity to reveal the intention of the design. Type names, method names, and argument names all combine to form an INTENTION-REVEALING INTERFACE.
所以:
Therefore:
命名类和操作时,应着重描述其效果和用途,无需提及实现方式。这样可以减轻客户端开发人员理解内部机制的负担。这些名称应遵循通用语言(UBIQUITOUS LANGUAGE),以便团队成员能够快速推断其含义。在创建行为之前,先为其编写测试,以迫使自己从客户端开发人员的角度思考。
Name classes and operations to describe their effect and purpose, without reference to the means by which they do what they promise. This relieves the client developer of the need to understand the internals. These names should conform to the UBIQUITOUS LANGUAGE so that team members can quickly infer their meaning. Write a test for a behavior before creating it, to force your thinking into client developer mode.
所有复杂的机制都应该封装在抽象接口之后,这些接口用意图而不是手段来表达。
All the tricky mechanism should be encapsulated behind abstract interfaces that speak in terms of intentions, rather than means.
在领域的公共接口中,只需阐明关系和规则,无需说明如何执行;描述事件和动作,无需说明如何执行;提出方程式,无需给出求解的数值方法。提出问题,但不要给出找到答案的方法。
In the public interfaces of the domain, state relationships and rules, but not how they are enforced; describe events and actions, but not how they are carried out; formulate the equation but not the numerical method to solve it. Pose the question, but don’t present the means by which the answer shall be found.
一款面向油漆商店的程序可以向顾客展示标准油漆的混合效果。以下是初始设计,它只有一个域类。
A program for paint stores can show a customer the result of mixing standard paints. Here is the initial design, which has a single domain class.
图 10.2
Figure 10.2
要想猜测该paint(Paint)方法的作用,唯一的办法就是阅读代码。
The only way to even guess what the paint(Paint) method does is to read the code.
public void paint(Paint paint) {
v = v + paint.getV(); //After mixing, volume is summed
// Omitted many lines of complicated color mixing logic
// ending with the assignment of new r, b, and y values.
}
好的,看来这种方法是将两种颜料混合在一起,结果体积更大,颜色也混合了。
OK, so it looks like this method combines two Paints together, the result having a larger volume and a mixed color.
为了转换视角,我们来为这个方法编写一个测试。(这段代码基于 JUnit 测试框架。)
To shift our perspective, let’s write a test for this method. (This code is based on the JUnit test framework.)
public void testPaint() {
// Create a pure yellow paint with volume=100
Paint yellow = new Paint(100.0, 0, 50, 0);
// Create a pure blue paint with volume=100
Paint blue = new Paint(100.0, 0, 0, 50);
// Mix the blue into the yellow
yellow.paint(blue);
// Result should be volume of 200.0 of green paint
assertEquals(200.0, yellow.getV(), 0.01);
assertEquals(25, yellow.getB());
assertEquals(25, yellow.getY());
assertEquals(0, yellow.getR());
}
通过测试只是起点。但目前来看,这并不能令人满意,因为测试代码并没有告诉我们它到底在做什么。让我们重写测试,使其反映出如果我们在编写客户端应用程序时会如何使用Paint对象。最初,这个测试会失败。事实上,它甚至无法编译。我们编写这个测试的目的是从客户端开发者的角度来探索Paint对象的接口设计。
The passing test is the starting point. It is unsatisfying at this point because the code in the test doesn’t tell us what it is doing. Let’s rewrite the test to reflect the way we would like to use the Paint objects if we were writing a client application. Initially, this test will fail. In fact, it won’t even compile. We are writing it to explore the interface design of the Paint object from the client developer’s point of view.
public void testPaint() {
// Start with a pure yellow paint with volume=100
Paint ourPaint = new Paint(100.0, 0, 50, 0);
// Take a pure blue paint with volume=100
Paint blue = new Paint(100.0, 0, 0, 50);
// Mix the blue into the yellow
ourPaint.mixIn(blue);
// Result should be volume of 200.0 of green paint
assertEquals(200.0, ourPaint.getVolume(), 0.01);
assertEquals(25, ourPaint.getBlue());
assertEquals(25, ourPaint.getYellow());
assertEquals(0, ourPaint.getRed());
}
我们应该花时间编写一个测试,使其反映我们希望与这些对象交互的方式。之后,我们重构Paint类,使测试通过。
We should take our time to write a test that reflects the way we would like to talk to these objects. After that, we refactor the Paint class to make the test pass.
图 10.3
Figure 10.3
新的方法名可能无法完全说明“混合”另一个Paint 对象会产生什么效果(这需要用到断言,稍后会讲到)。但它足以让读者开始使用这个类,尤其是在结合测试提供的示例来看。而且,它还能帮助客户端代码的读者理解客户端的意图。在本章接下来的几个示例中,我们将再次重构这个类,使其更加清晰易懂。
The new method name may not tell the reader everything about the effect of “mixing in” another Paint (for that we’ll need ASSERTIONS, coming up in a few pages). But it will clue the reader in enough to get started using the class, especially with the example the test provides. And it will allow the reader of the client code to interpret the client’s intent. In the next few examples in this chapter, we’ll refactor this class again to make it even clearer.
整个子域可以被划分成独立的模块,并封装在能够揭示意图的接口之后。第 15 章“提炼”将结合内聚机制和通用子域,更详细地讨论如何利用这种划分方法来聚焦项目并管理大型系统的复杂性。
Entire subdomains can be carved off into separate modules and encapsulated behind INTENTION-REVEALING INTERFACES. Using such whittling to focus a project and manage the complexity of a large system will be discussed more in Chapter 15, “Distillation,” with COHESIVE MECHANISMS and GENERIC SUBDOMAINS.
但在接下来的两种模式中,我们将致力于使方法的使用后果非常可预测。复杂的逻辑可以在无副作用函数中安全地实现。改变系统状态的方法可以用断言来描述。
But in the next two patterns, we’ll set out to make the consequences of using a method very predictable. Complex logic can be done safely in SIDE-EFFECT-FREE FUNCTIONS. Methods that change system state can be characterized with ASSERTIONS.
操作大致可以分为两类:命令和查询。查询从系统中获取信息,可能只是简单地访问变量中的数据,也可能基于这些数据执行计算。命令(也称为修饰符)是对系统产生某种影响的操作(例如,通过设置变量)。在标准英语中,“副作用”一词指的是意料之外的后果,但在计算机科学中,它指的是对系统状态的任何影响。就我们的目的而言,我们将其含义限定为任何会影响未来操作的系统状态变化。
Operations can be broadly divided into two categories, commands and queries. Queries obtain information from the system, possibly by simply accessing data in a variable, possibly performing a calculation based on that data. Commands (also known as modifiers) are operations that affect some change to the systems (for a simple example, by setting a variable). In standard English, the term side effect implies an unintended consequence, but in computer science, it means any effect on the state of the system. For our purposes, let’s narrow that meaning to any change in the state of the system that will affect future operations.
为什么会采用“副作用”这个术语,并将其用于描述操作所导致的有意变更?我认为这是基于复杂系统的经验。大多数操作都会调用其他操作,而被调用的操作又会调用其他操作。一旦涉及这种任意深度的嵌套,就很难预料到调用某个操作的所有后果。客户端的开发人员可能并未预料到第二层和第三层操作会产生这些影响——它们在各个层面上都成了副作用。复杂设计中的各个元素还会以其他方式相互作用,这些方式也可能产生同样的不可预测性。“副作用”一词的使用强调了这种相互作用的必然性。
Why was the term side effect adopted and applied to quite intentional changes affected by operations? I assume this was based on experience with complex systems. Most operations call on other operations, and those called invoke still other operations. As soon as this arbitrarily deep nesting is involved, it becomes very hard to anticipate all the consequences of invoking an operation. The developer of the client may not have intended the effects of the second-tier and third-tier operations—they’ve become side effects in every sense of the phrase. Elements of a complex design interact in other ways that are likely to produce the same unpredictability. The use of the term side effect underlines the inevitability of that interaction.
多个规则或计算组合的交互变得极其难以预测。调用操作的开发者必须理解该操作的实现及其所有委托的实现,才能预判结果。如果开发者被迫揭开底层细节,那么任何接口抽象的效用都将受到限制。如果没有安全可预测的抽象,开发者就必须限制组合爆炸,从而限制可构建行为的丰富程度。
Interactions of multiple rules or compositions of calculations become extremely difficult to predict. The developer calling an operation must understand its implementation and the implementation of all its delegations in order to anticipate the result. The usefulness of any abstraction of interfaces is limited if the developers are forced to pierce the veil. Without safely predictable abstractions, the developers must limit the combinatory explosion, placing a low ceiling on the richness of behavior that is feasible to build.
能够返回结果而不产生副作用的操作称为函数。一个函数可以被多次调用,每次都返回相同的值。一个函数可以调用其他函数,而无需担心嵌套深度。与具有副作用的操作相比,函数更容易测试。因此,函数可以降低风险。
Operations that return results without producing side effects are called functions. A function can be called multiple times and return the same value each time. A function can call on other functions without worrying about the depth of nesting. Functions are much easier to test than operations that have side effects. For these reasons, functions lower risk.
显然,在大多数软件系统中,命令是不可避免的,但可以通过两种方式缓解这个问题。首先,可以将命令和查询严格分离到不同的操作中。确保导致变更的方法不返回域数据,并且尽可能保持简洁。在不会产生可观察副作用的方法中执行所有查询和计算(Meyer 1988)。
Obviously, you can’t avoid commands in most software systems, but the problem can be mitigated in two ways. First, you can keep the commands and queries strictly segregated in different operations. Ensure that the methods that cause changes do not return domain data and are kept as simple as possible. Perform all queries and calculations in methods that cause no observable side effects (Meyer 1988).
其次,通常存在一些替代模型和设计,它们完全不需要修改现有对象。相反,它们会创建一个新的值对象(VALUE OBJECT)来表示计算结果并返回。这是一种常见的技术,将在下面的示例中进行说明。值对象可以根据查询创建、传递,然后就可以被遗忘——这与实体(ENTITY)不同,实体的生命周期受到严格控制。
Second, there are often alternative models and designs that do not call for an existing object to be modified at all. Instead, a new VALUE OBJECT, representing the result of the computation, is created and returned. This is a common technique, which will be illustrated in the example that follows. A VALUE OBJECT can be created in answer to a query, handed off, and forgotten—unlike an ENTITY, whose life cycle is carefully regulated.
值对象是不可变的,这意味着除了仅在创建时调用的初始化器之外,它们的所有操作都是函数。值对象与函数一样,使用起来更安全,也更容易测试。将逻辑或计算与状态更改混合的操作应该重构为两个独立的操作(Fowler 1999,第 279 页)。但根据定义,这种将副作用分离到简单命令方法中的做法仅适用于实体。在完成将修改与查询分离的重构之后,可以考虑进行第二次重构,将复杂计算的职责移至值对象中。通常,通过派生值对象而不是更改现有状态,或者通过将所有职责移至值对象中,可以完全消除副作用。
VALUE OBJECTS are immutable, which implies that, apart from initializers called only during creation, all their operations are functions. VALUE OBJECTS, like functions, are safer to use and easier to test. An operation that mixes logic or calculations with state change should be refactored into two separate operations (Fowler 1999, p. 279). But by definition, this segregation of side effects into simple command methods only applies to ENTITIES. After completing the refactoring to separate modification from querying, consider a second refactoring to move the responsibility for the complex calculations into a VALUE OBJECT. The side effect often can be completely eliminated by deriving a VALUE OBJECT instead of changing existing state, or by moving the entire responsibility into a VALUE OBJECT.
所以:
Therefore:
尽可能将程序逻辑放入函数中,这些函数和操作返回的结果不会产生可观察的副作用。严格地将命令(会导致可观察状态发生改变的方法)分离成不返回领域信息的非常简单的操作。当出现符合职责的概念时,通过将复杂的逻辑移入值对象来进一步控制副作用。
Place as much of the logic of the program as possible into functions, operations that return results with no observable side effects. Strictly segregate commands (methods that result in modifications to observable state) into very simple operations that do not return domain information. Further control side effects by moving complex logic into VALUE OBJECTS when a concept fitting the responsibility presents itself.
无副作用函数,尤其是在不可变值对象中,可以安全地组合各种操作。当函数通过意图揭示接口呈现时,开发者无需了解其实现细节即可使用它。
SIDE-EFFECT-FREE FUNCTIONS, especially in immutable VALUE OBJECTS, allow safe combination of operations. When a FUNCTION is presented through an INTENTION-REVEALING INTERFACE, a developer can use it without understanding the detail of its implementation.
一款面向油漆商店的程序可以向顾客展示标准油漆的混合效果。接着上一个例子,这里是单域类。
A program for paint stores can show a customer the result of mixing standard paints. Picking up where we left off in the last example, here is the single domain class.
图 10.4
Figure 10.4
public void mixIn(Paint other) {
volume = volume.plus(other.getVolume());
// Many lines of complicated color-mixing logic
// ending with the assignment of new red, blue,
// and yellow values.
}
mixIn()图 10.5. 该方法的副作用
Figure 10.5. The side effects of the mixIn() method
该方法中涉及很多操作mixIn(),但这种设计确实遵循了修改与查询分离的原则。稍后我们将讨论的一个问题是 paint 2 对象的体积,该方法的论证结果mixIn()悬而未决。Paint 2 的体积在操作后没有改变,这在这个概念模型的背景下似乎不太合乎逻辑。对于最初的开发者来说,这并非问题,因为据我们所知,他们在操作后对 paint 2 对象并不感兴趣,但副作用的后果,或者副作用的缺失,都难以预料。我们将在稍后讨论断言时再次探讨这个问题。现在,让我们来看看颜色。
A lot is happening in the mixIn() method, but this design does follow the rule of separating modification from querying. One concern, which we’ll take up later, is that the volume of the paint 2 object, the argument of the mixIn() method, has been left in limbo. Paint 2’s volume is unchanged by the operation, which doesn’t seem quite logical in the context of this conceptual model. This was not a problem for the original developers because, as near as we can tell, they had no interest in the paint 2 object after the operation, but it is hard to anticipate the consequences of side effects or their absence. We’ll return to this question soon in the discussion of ASSERTIONS. For now, let’s look at color.
颜色是这个领域的一个重要概念。我们不妨尝试将其明确地定义为一个对象。它应该叫什么呢?“颜色”这个词首先浮现在脑海中,但之前的知识分析已经让我们意识到,颜料的颜色混合方式与我们更熟悉的RGB灯光显示方式截然不同。这个名称需要体现出这一点。
Color is an important concept in this domain. Let’s try the experiment of making it an explicit object. What should it be called? “Color” comes to mind first, but earlier knowledge crunching had already yielded the important insight that color mixing is different for paint than it is for the more familiar RGB light display. The name needs to reflect this.
图 10.6
Figure 10.6
提取出颜料颜色对象确实比之前的版本传递了更多信息,但计算过程仍然相同,仍在mixIn()方法中。当我们移出颜色数据时,应该同时移除相关的行为。在此之前,请注意颜料颜色对象是一个值对象(VALUE OBJECT)。因此,它应该被视为不可变对象。当我们混合颜料时,颜料对象本身发生了变化。它是一个具有持续生命周期的实体(ENTITY)。相比之下,代表特定黄色色调的颜料颜色对象始终保持不变。混合颜料只会生成一个新的颜料颜色对象,代表新的颜色。
Factoring out Pigment Color does communicate more than the earlier version, but the computation is the same, still in the mixIn() method. When we moved out the color data, we should have taken related behavior with it. Before we do, note that Pigment Color is a VALUE OBJECT. Therefore, it should be treated as immutable. When we mixed paint, the Paint object itself was changed. It was an ENTITY with an ongoing life story. In contrast, a Pigment Color representing a particular shade of yellow is always exactly that. Instead, mixing will result in a new Pigment Color object representing the new color.
图 10.7
Figure 10.7
public class PigmentColor {
public PigmentColor mixedWith(PigmentColor other,
double ratio) {
// Many lines of complicated color-mixing logic
// ending with the creation of a new PigmentColor object
// with appropriate new red, blue, and yellow values.
}
}
public class Paint {
public void mixIn(Paint other) {
volume = volume + other.getVolume();
double ratio = other.getVolume() / volume;
pigmentColor =
pigmentColor.mixedWith(other.pigmentColor(), ratio);
}
}
图 10.8
Figure 10.8
现在Paint中的修改代码已经尽可能简化。新的Pigment Color类能够捕获并明确地传递信息,它提供了一个无副作用的函数,其结果易于理解、易于测试,并且可以安全地使用或与其他操作结合使用。由于其安全性极高,复杂的颜色混合逻辑得到了真正的封装。使用该类的开发人员无需了解其实现细节。
Now the modification code in Paint is as simple as possible. The new Pigment Color class captures knowledge and communicates it explicitly, and it provides a SIDE-EFFECT-FREE FUNCTION whose result is easy to understand, easy to test, and safe to use or combine with other operations. Because it is so safe, the complex logic of color mixing is truly encapsulated. Developers using this class don’t have to understand the implementation.
将复杂的计算拆分成无副作用的函数可以大大缩小问题规模,但实体上仍然会残留一些会产生副作用的命令,任何使用这些命令的人都必须了解其后果。断言(SSERTION)则能使副作用更加明确,也更容易处理。
Separating complex computations into SIDE-EFFECT-FREE FUNCTIONS cuts the problem down to size, but there is still a residue of commands on the ENTITIES that produce side effects, and anyone using them must understand their consequences. ASSERTIONS make side effects explicit and easier to deal with.
没错,一条不包含复杂计算的命令可能很容易通过观察来理解。但在由多个小部分组成的复杂设计中,一条命令可能会调用其他命令。使用高层命令的开发者必须理解每个底层命令的后果。封装性就此失效。而且,由于对象接口不限制副作用,实现同一接口的两个子类可能具有不同的副作用。使用它们的开发者需要知道哪个是哪个,才能预判后果。抽象和多态性也就如此了。
True, a command containing no complex computations may be fairly easy to interpret by inspection. But in a design where larger parts are built of smaller ones, a command may invoke other commands. The developer using the high-level command must understand the consequences of each underlying command. So much for encapsulation. And because object interfaces do not restrict side effects, two subclasses that implement the same interface can have different side effects. The developer using them will want to know which is which to anticipate the consequences. So much for abstraction and polymorphism.
当操作的副作用仅由其实现隐式定义时,大量委托的设计就会变成错综复杂的因果关系。理解程序的唯一方法是沿着分支路径追踪执行过程。封装的价值荡然无存。追踪具体执行过程的必要性违背了抽象的初衷。
When the side effects of operations are only defined implicitly by their implementation, designs with a lot of delegation become a tangle of cause and effect. The only way to understand a program is to trace execution through branching paths. The value of encapsulation is lost. The necessity of tracing concrete execution defeats abstraction.
我们需要一种方法来理解设计元素的含义以及执行操作的后果,而无需深入探究其内部机制。意图揭示接口(INTENTION-REVEALING INTERFACES)可以帮助我们实现这一目标,但非正式的意图暗示并不总是足够。“契约式设计”学派更进一步,对类和方法做出开发者保证为真的“断言”。Meyer 1988 年的著作详细讨论了这种风格。简而言之,“后置条件”描述了操作的副作用,即调用方法的保证结果。“前置条件”就像合同上的细则,是后置条件保证成立必须满足的条件。类不变量对对象在任何操作结束时的状态做出断言。不变量也可以为整个聚合(AGGREGATES)声明,从而严格定义完整性规则。
We need a way of understanding the meaning of a design element and the consequences of executing an operation without delving into its internals. INTENTION-REVEALING INTERFACES carry us part of the way there, but informal suggestions of intentions are not always enough. The “design by contract” school goes the next step, making “assertions” about classes and methods that the developer guarantees will be true. This style is discussed in detail in Meyer 1988. Briefly, “post-conditions” describe the side effects of an operation, the guaranteed outcome of calling a method. “Preconditions” are like the fine print on the contract, the conditions that must be satisfied in order for the post-condition guarantee to hold. Class invariants make assertions about the state of an object at the end of any operation. Invariants can also be declared for entire AGGREGATES, rigorously defining integrity rules.
所有这些断言描述的是状态而非过程,因此更容易分析。类不变量有助于刻画类的含义,并通过提高对象的可预测性来简化客户端开发人员的工作。如果您信任后置条件的保证,则无需担心方法的具体工作方式。委托的影响应该已经包含在断言中。
All these assertions describe state, not procedures, so they are easier to analyze. Class invariants help characterize the meaning of a class, and simplify the client developer’s job by making the objects more predictable. If you trust the guarantee of a post-condition, you don’t have to worry about how a method works. The effects of delegations should already be incorporated into the assertions.
所以:
Therefore:
明确操作的后置条件以及类和聚合的不变量。如果断言无法直接用你的编程语言编写,请为其编写自动化单元测试。根据项目开发流程的风格,将它们写入文档或图表中。
State post-conditions of operations and invariants of classes and AGGREGATES. If ASSERTIONS cannot be coded directly in your programming language, write automated unit tests for them. Write them into documentation or diagrams where it fits the style of the project’s development process.
寻找具有连贯概念集的模型,引导开发人员推断出预期的断言,从而加快学习曲线并降低代码矛盾的风险。
Seek models with coherent sets of concepts, which lead a developer to infer the intended ASSERTIONS, accelerating the learning curve and reducing the risk of contradictory code.
尽管许多面向对象语言目前并不直接支持断言(ASSERTION) ,但断言仍然是一种强大的设计思维方式。自动化单元测试可以部分弥补语言支持的不足。由于断言是基于状态而非过程的,因此编写测试非常容易。测试设置会预先设置前提条件;然后,在执行之后,测试会检查后置条件是否成立。
Even though many object-oriented languages don’t currently support ASSERTIONS directly, ASSERTIONS are still a powerful way of thinking about a design. Automated unit tests can partially compensate for the lack of language support. Because ASSERTIONS are all in terms of states, rather than procedures, they make tests easy to write. The test setup puts the preconditions in place; then, after execution, the test checks to see if the post-conditions hold.
清晰明确的不变量以及前置条件和后置条件能够帮助开发者理解使用某个操作或对象的后果。理论上,任何不矛盾的断言集都可以。但人们并非只是简单地在脑海中构建谓词。他们会对模型的概念进行外推和内插,因此找到既能满足应用程序需求又易于理解的模型至关重要。
Clearly stated invariants and pre- and post-conditions allow a developer to understand the consequences of using an operation or object. Theoretically, any noncontradictory set of assertions would work. But humans don’t just compile predicates in their heads. They will be extrapolating and interpolating the concepts of the model, so it is important to find models that make sense to people as well as satisfying the needs of the application.
回想一下,在前面的例子中,我担心的是PaintmixIn(Paint)类上操作的参数会发生什么情况的歧义。
Recall that in the previous example I was concerned about the ambiguity of what happens to the argument of the mixIn(Paint) operation on the Paint class.
图 10.9
Figure 10.9
接收器的音量会增加参数音量的量。根据我们对物理颜料的一般理解,这种混合过程应该会消耗掉另一种颜料等量的体积,使其最终体积为零,或者完全消失。当前的实现方式不会修改参数,而修改参数本身就是一种风险极高的副作用。
The receiver’s volume is increased by the amount of the argument’s volume. Drawing on our general understanding of physical paint, this mixing process should deplete the other paint by the same amount, draining it to zero volume, or eliminating it completely. The current implementation does not modify the argument, and modifying arguments is a particularly risky kind of side effect anyway.
为了打好基础,我们先来陈述一下该方法的后置mixIn()条件:
To start on a solid footing, let’s state the post-condition of the mixIn() method as it is:
后p1.mixIn(p2):
After p1.mixIn(p2):
p1.volume增加的量为p2.volume。
p1.volume is increased by amount of p2.volume.
p2.volume未改变。
p2.volume is unchanged.
问题在于,开发者会犯错,因为这些属性与我们引导他们思考的概念不符。直接的解决方法是将另一种颜料的体积设为零。修改参数是一种不好的做法,但这样做简单直观。我们可以声明一个不变式:
The trouble is, developers are going to make mistakes, because these properties don’t fit the concepts we have invited them to think about. The straightforward fix would be change the volume of the other paint to zero. Changing an argument is a bad practice, but it would be easy and intuitive. We could state an invariant:
混合后,油漆的总体积不变。
Total volume of paint is unchanged by mixing.
等等!开发人员在考虑这个方案时,突然有了新的发现。原来,最初的设计者这样安排是有充分理由的。程序最后会列出所有添加的未混合颜料。毕竟,这款应用的最终目的就是帮助用户确定应该将哪些颜料混合在一起。
But wait! While developers were pondering this option, they made a discovery. It turns out that there was a compelling reason the original designers made it this way. At the end, the program reports the list of unmixed paints that were added. After all, the ultimate purpose of this application is to help a user figure out which paints to put into a mixture.
因此,为了使容量模型在逻辑上保持一致,它就无法满足应用需求了。这似乎是一个两难困境。我们是否只能继续记录这个奇怪的后置条件,并试图通过良好的沟通来弥补?世事并非总是直观的,有时这反而是最佳方案。但就此例而言,这种不便之处似乎表明某些概念缺失。让我们寻找一个新的模型吧。
So, to make the volume model logically consistent would make it unsuitable for its application requirements. There seems to be a dilemma. Are we stuck with documenting the weird post-condition and trying to compensate with good communication? Not everything in this world is intuitive, and sometimes that is the best answer. But in this case, the awkwardness seems to point to missing concepts. Let’s look for a new model.
在寻找更优模型的过程中,由于期间积累了丰富的知识并进行了重构,获得了更深刻的理解,我们比最初的设计者拥有显著优势。例如,我们使用对值对象执行无副作用函数来计算颜色。这意味着我们可以随时重复计算。我们应该充分利用这一优势。
As we search for a better model, we have significant advantages over the original designers, because of the knowledge crunching and refactoring to deeper insight that has happened in the interim. For example, we compute color using a SIDE-EFFECT-FREE FUNCTION on a VALUE OBJECT. This means we can repeat the calculation any time we need to. We should take advantage of that.
我们似乎赋予了Paint两项不同的基本职责。让我们尝试将它们分开。
We seem to be giving Paint two different basic responsibilities. Let’s try splitting them.
现在只有一个命令,mixIn()它只是将一个对象添加到集合中,这种效果从对模型的直观理解中显而易见。所有其他操作都是无副作用的函数。
Now there is only one command, mixIn(). It just adds an object to a collection, an effect apparent from an intuitive understanding of the model. All other operations are SIDE-EFFECT-FREE FUNCTIONS.
用于验证图 10.10中列出的某个断言的测试方法可能如下所示(使用 JUnit 测试框架):
A test method confirming one of the ASSERTIONS listed in Figure 10.10 could look something like this (using the JUnit test framework):
public void testMixingVolume {
PigmentColor yellow = new PigmentColor(0, 50, 0);
PigmentColor blue = new PigmentColor(0, 0, 50);
StockPaint paint1 = new StockPaint(1.0, yellow);
StockPaint paint2 = new StockPaint(1.5, blue);
MixedPaint mix = new MixedPaint();
mix.mixIn(paint1);
mix.mixIn(paint2);
assertEquals(2.5, mix.getVolume(), 0.01);
}
图 10.10
Figure 10.10
该模型能够更全面地捕捉和传达领域信息。其不变量和后置条件符合常理,因此更易于维护和使用。
This model captures and communicates more of the domain. The invariants and post-conditions make common sense, which will make them easier to maintain and use.
意图揭示接口的沟通性,加上无副作用函数和断言带来的可预测性,应该能够确保封装和抽象的安全性。
The communicativeness of the INTENTION-REVEALING INTERFACES, combined with the predictability given by SIDE-EFFECT-FREE FUNCTIONS and ASSERTIONS, should make encapsulation and abstraction safe.
可重组元素的下一个要素是有效分解……
The next ingredient in recombinable elements is effective decomposition. . . .
有时人们会细化功能以实现灵活组合,有时则会将其合并成一个整体来封装复杂性,有时则会追求一致的粒度,使所有类和操作都达到相似的规模。这些都是过于简化的做法,并不适合作为通用规则。但它们的出发点是解决一系列基本问题。
Sometimes people chop functionality fine to allow flexible combination. Sometimes they lump it large to encapsulate complexity. Sometimes they seek a consistent granularity, making all classes and operations to a similar scale. These are oversimplifications that don’t work well as general rules. But they are motivated by a basic set of problems.
当模型或设计的各个元素被嵌入到一个整体式结构中时,它们的功能就会重复。外部界面无法传达客户可能关心的所有信息。由于不同的概念混杂在一起,它们的含义难以理解。
When elements of a model or design are embedded in a monolithic construct, their functionality gets duplicated. The external interface doesn’t say everything a client might care about. Their meaning is hard to understand, because different concepts are mixed together.
另一方面,将类和方法拆解开来可能会不必要地增加客户端的复杂性,迫使客户端对象去理解那些细小的组成部分是如何组合在一起的。更糟糕的是,某些概念甚至可能完全丢失。半个铀原子并不是铀。当然,重要的不仅仅是晶粒的大小,还有晶粒的走向。
On the other hand, breaking down classes and methods can pointlessly complicate the client, forcing client objects to understand how tiny pieces fit together. Worse, a concept can be lost completely. Half of a uranium atom is not uranium. And of course, it isn’t just grain size that counts, but just where the grain runs.
照搬照抄的规则行不通。但大多数领域都存在着某种深层的逻辑一致性,否则它们就无法在各自的领域内生存。这并非意味着各个领域都完全一致,人们谈论它们的方式也并非总是前后一致。但其中必然蕴含着某种规律和逻辑,否则建模就毫无意义。正是由于这种潜在的一致性,当我们找到一个与领域某些部分相契合的模型时,它更有可能与我们之后发现的其他部分保持一致。有时,新的发现并不容易让模型适应,在这种情况下,我们会根据更深刻的理解重构模型,并希望它能够适应下一个发现。
Cookbook rules don’t work. But there is a logical consistency deep in most domains, or else they would not be viable in their own sphere. This is not to say that domains are perfectly consistent, and certainly the ways people talk about them are not consistent. But there is rhyme and reason somewhere, or else modeling would be pointless. Because of this underlying consistency, when we find a model that resonates with some part of the domain, it is more likely to be consistent with other parts that we discover later. Sometimes the new discovery isn’t easy for the model to adapt to, in which case we refactor to deeper insight, and hope to conform to the next discovery.
这就是为什么反复重构最终会带来灵活性的原因之一。随着代码适应新理解的概念或需求,概念轮廓逐渐显现。
This is one reason why repeated refactoring eventually leads to suppleness. The CONCEPTUAL CONTOURS emerge as the code is adapted to newly understood concepts or requirements.
高内聚和低耦合这两个基本原则在各个层面的设计中都发挥着作用,从单个方法到类和模块,再到大型结构(参见第16章)。这两个原则不仅适用于代码,也适用于概念。为了避免陷入机械式的理解,你需要经常运用对领域的直觉来调整你的技术思维。在做每一个决定时,都要问问自己:“这是否是一种权宜之计?”它是基于当前模型和代码中的一组特定关系,还是反映了底层领域的某些轮廓?
The twin fundamentals of high cohesion and low coupling play a role in design at all scales, from individual methods up through classes and MODULES to large-scale structures (see Chapter 16). These two principles apply to concepts as much as to code. To avoid slipping into a mechanistic view of them, temper your technical thinking by frequently touching base with your intuition for the domain. With each decision, ask yourself, “Is this an expedient based on a particular set of relationships in the current model and code, or does it echo some contour of the underlying domain?”
找到概念上有意义的功能单元,最终的设计将既灵活又易于理解。例如,如果两个对象的“相加”在领域内具有明确的含义,那么就在该层级实现方法。不要将操作拆分add()成两个步骤。不要在同一操作中进入下一步。从更大的层面来看,每个对象都应该是一个完整的概念,一个“整体价值”。
Find the conceptually meaningful unit of functionality, and the resulting design will be both flexible and understandable. For example, if an “addition” of two objects has a coherent meaning in the domain, then implement methods at that level. Don’t break the add() into two steps. Don’t proceed to the next step within the same operation. On a slightly larger scale, each object should be a single complete concept, a “WHOLE VALUE.”1
同样,在任何领域,总有一些细节对于软件服务的用户来说并不重要。我们假设的调色应用程序的用户不会单独添加红色颜料或蓝色颜料;他们混合的是包含所有三种颜料的完整颜料。将无需分解或重新排列的元素归类,可以避免界面杂乱,并使用户更容易看到真正需要重新组合的元素。如果用户的物理设备允许添加单个颜料,那么调色领域就会发生改变,单个颜料也可能被操控。颜料化学家需要更精细的控制,这将涉及完全不同的分析,并可能生成比我们用于调色的抽象颜料颜色模型更详细的颜料成分模型。但这对于参与调色应用程序项目的任何人来说都无关紧要。
By the same token, there are areas in any domain where detail isn’t interesting to the kind of people the software serves. The users of our hypothetical paint mixing application don’t add red pigment or blue pigment; they combine complete paints, which contain all three pigments. Clumping things that don’t need to be dissected or rearranged avoids clutter and makes it easier to see the elements that really are meant to recombine. If our users’ physical equipment allowed individual pigments to be added, the domain would be altered, and the individual pigments might be manipulated. A paint chemist would need still finer control, which would involve a whole other analysis, probably producing a much more detailed model of the makeup of paint than our abstracted pigment color that serves paint mixing. But it is simply irrelevant to anyone involved in the paint mixing application project.
所以:
Therefore:
将设计元素(操作、接口、类和聚合)分解成内聚单元,同时考虑你对领域重要划分的直觉。观察连续重构过程中的变化轴和稳定性轴,并寻找解释这些变化模式的底层概念轮廓。使模型与领域中使其成为可行知识领域的固有特征保持一致。
Decompose design elements (operations, interfaces, classes, and AGGREGATES) into cohesive units, taking into consideration your intuition of the important divisions in the domain. Observe the axes of change and stability through successive refactorings and look for the underlying CONCEPTUAL CONTOURS that explain these shearing patterns. Align the model with the consistent aspects of the domain that make it a viable area of knowledge in the first place.
目标是创建一组简洁的接口,这些接口能够逻辑地组合起来,用通用语言(UBIQUITOUS LANGUAGE)表达合理的语句,同时避免无关选项带来的干扰和维护负担。这通常是重构的结果:很难直接生成这些接口。前端。但这可能永远不会源于技术导向的重构;它源于对更深层次洞察的重构。
The goal is a simple set of interfaces that combine logically to make sensible statements in the UBIQUITOUS LANGUAGE, and without the distraction and maintenance burden of irrelevant options. This is typically an outcome of refactoring: it’s hard to produce up front. But it may never emerge from technically oriented refactoring; it emerges from refactoring toward deeper insight.
即使设计遵循概念轮廓,也仍然需要进行修改和重构。如果后续的重构倾向于局部化,而不是撼动模型的多个宏观概念,则表明模型拟合良好。如果遇到需要对对象和方法进行大规模修改的需求,则表明我们对领域的理解需要进一步完善。这为我们提供了一个机会,可以深化模型并使设计更加灵活。
Even when the design follows CONCEPTUAL CONTOURS, there will need to be modifications and refactoring. When successive refactoring tends to be localized, not shaking multiple broad concepts of the model, it is an indicator of model fit. Encountering a requirement that forces extensive changes in the breakdown of the objects and methods is a message: Our understanding of the domain needs refinement. It presents an opportunity to deepen the model and make the design more supple.
第九章中,基于对会计概念的更深入理解,对贷款跟踪系统进行了重构:
In Chapter 9, a loan tracking system was refactored based on deeper insight into accounting concepts:
图 10.11
Figure 10.11
新模型只比旧模型多了一个对象,但责任划分却发生了很大的变化。
The new model contained only one more object than the old one, yet the partitioning of responsibility had been greatly changed.
计算器类中通过案例逻辑生成的日程表被拆分成多个独立的类,分别对应不同类型的费用和利息。另一方面,之前分开处理的费用和利息支付则被合并在一起。
Schedules, which had been worked out through case logic in the Calculator classes, were exploded into discrete classes for different types of fees and interest. On the other hand, payments of fees and interest, previously kept separate, were lumped together.
由于新明确的概念引起了共鸣,并且应计计划层级结构具有凝聚力,开发人员认为该模型更好地遵循了该领域的一些概念轮廓。
Because of the resonance of the newly explicit concepts and the cohesiveness of the Accrual Schedule hierarchy, the developer believed that this model better follows some of the domain’s CONCEPTUAL CONTOURS.
图 10.12。该模型可以添加新的应计计划类型。
Figure 10.12. This model accommodates adding new kinds of Accrual Schedules.
开发人员唯一能确定的变化是新增应计计划。这些需求早已蓄势待发。因此,除了让现有功能更清晰简洁之外,她还选择了一种便于引入新计划的模型。但她是否找到了一个概念框架,能够帮助领域设计随着应用程序和业务的发展而不断变化和扩展呢?设计如何应对意外变化无法保证,但她认为这提高了成功的几率。
The one change the developer could confidently predict was the addition of new Accrual Schedules. Those requirements were already waiting in the wings. So in addition to making existing functionality clearer and simpler, she chose a model that would make it easy to introduce new schedules. But had she found a CONCEPTUAL CONTOUR that will help the domain design change and grow as the application and the business evolve? There can be no guarantees about how a design will handle unanticipated change, but she thought it had improved the odds.
随着项目的推进,出现了对处理提前付款和逾期付款的详细规则的需求。开发人员在研究这个问题时欣喜地发现,利息支付和手续费支付的规则几乎完全相同。这意味着新的模型元素可以自然地与单一的Payment类连接起来。
As the project proceeded, a requirement emerged for detailed rules for handling early and late payments. As she studied the problem, the developer was pleased to see that virtually the same rules applied to payments on interest and to payments on fees. This meant that the new model elements would connect naturally to the single Payment class.
图 10.13
Figure 10.13
旧设计会导致两个支付历史类之间出现重复代码。(这种困难或许会促使她意识到支付类应该共享,从而衍生出另一种类似的模型。)这种易于扩展的特性并非源于她预见到这种变化,也并非源于她设计了一个能够适应任何可预见变化的通用方案。而是因为在之前的重构中,设计与领域底层概念保持一致。
The old design would have forced duplication between the two Payment History classes. (This difficulty might have triggered an insight that the Payment class should be shared, leading by another path to a similar model.) This ease of extension did not come because she anticipated the change. Nor did it come because she made a design so versatile it could accommodate any conceivable change. It happened because in the previous refactoring, the design was aligned with underlying concepts of the domain.
意图揭示界面允许客户端将对象呈现为意义单元,而不仅仅是机制。无副作用函数和断言确保了这些单元的安全性,并支持进行复杂的组合。概念轮廓的出现稳定了模型的部分结构,也使单元的使用和组合更加直观。
INTENTION-REVEALING INTERFACES allow clients to present objects as units of meaning rather than just mechanisms. SIDE-EFFECT-FREE FUNCTIONS and ASSERTIONS make it safe to use those units and make complex combinations. The emergence of CONCEPTUAL CONTOURS stabilizes parts of the model and also makes the units more intuitive to use and combine.
当相互依存关系迫使我们同时考虑太多此类事项时,我们仍然会面临概念过载的问题……
We can still run into conceptual overload when interdependencies force us to think about too many of these things at a time. . . .
相互依赖关系使得模型和设计难以理解,也使得测试和维护变得困难,而且相互依赖关系很容易累积。
Interdependencies make models and designs hard to understand. They also make them hard to test and maintain. And interdependencies pile up easily.
当然,任何关联都是一种依赖关系,理解一个类就需要理解它所关联的对象。这些关联的对象还会关联到更多对象,这些关联对象也需要被理解。每个方法的每个参数的类型也是一种依赖关系。每个返回值也是如此。
Every association is, of course, a dependency, and understanding a class requires understanding what it is attached to. Those attached things will be attached to still more things, and they have to be understood too. The type of every argument of every method is also a dependency. So is every return value.
如果只有一个依赖项,你需要同时考虑两个类及其关系。如果有两个依赖项,你需要考虑这三个类中的每一个,以及它们彼此之间的关系。如果它们本身也有依赖关系,你还得注意这些依赖关系。如果有三个依赖项……情况就会变得非常复杂。
With one dependency, you have to think about two classes at the same time, and the nature of their relationship. With two dependencies, you have to think about each of the three classes, the nature of the class’s relationship to each of them, and any relationship they might have to each other. If they in turn have dependencies, you have to be wary of those also. With three dependencies . . . it snowballs.
模块和聚合都旨在限制相互依赖关系的复杂性。当一个高度内聚的子域被划分成一个模块时,一组对象就与系统的其余部分解耦,从而减少了相互关联的概念数量。但是,即使是模块,如果没有近乎狂热地致力于控制其内部的依赖关系,也需要仔细考虑很多因素。
Both MODULES and AGGREGATES are aimed at limiting the web of interdependencies. When a highly cohesive subdomain is carved out into a MODULE, a set of objects are decoupled from the rest of the system, so there are a finite number of interrelated concepts. But even a MODULE can be a lot to think about without an almost fanatical commitment to controlling dependencies within it.
即使在同一模块内,随着依赖项的增加,设计理解的难度也会急剧上升。这会增加开发人员的认知负荷,限制其能够处理的设计复杂度。隐式概念比显式引用更能加剧这种负担。
Even within a MODULE, the difficulty of interpreting a design increases wildly as dependencies are added. This adds to mental overload, limiting the design complexity a developer can handle. Implicit concepts contribute to this load even more than explicit references.
精炼模型经过不断提炼,直至概念间剩余的每一个联系都代表着这些概念意义的根本所在。在一个重要的子集中,依赖关系可以减少到零,从而得到一个仅凭自身以及一些基本元素和库概念就能完全理解的类。
Refined models are distilled until every remaining connection between concepts represents something fundamental to the meaning of those concepts. In an important subset, the number of dependencies can be reduced to zero, resulting in a class that can be fully understood all by itself, along with a few primitives and basic library concepts.
在任何编程环境中,总有一些基本概念无处不在,以至于人们总是会想起它们。例如,在 Java 开发中,基本类型和一些标准库提供了诸如数字、字符串和集合之类的基本概念。实际上,“整数”并不会增加额外的知识负担。除此之外,任何其他概念……为了理解某个事物而需要记住它,这会导致精神超负荷。
In every programming environment, a few basics are so pervasive that they are always in mind. For example, in Java development, primitives and a few standard libraries provide basics like numbers, strings, and collections. Practically speaking, “integers” don’t add to the intellectual load. Beyond that, every additional concept that has to be held in mind in order to understand an object contributes to mental overload.
隐含概念,无论是否已被识别,都与显式引用同等重要。虽然我们通常可以忽略对整数和字符串等基本值的依赖,但我们不能忽略它们所代表的含义。例如,在第一个颜料混合示例中,Paint对象包含三个公共整数,分别代表红色、黄色和蓝色颜色值。创建Pigment Color对象并没有增加涉及的概念数量或依赖关系,而是使已有的概念更加明确,更容易理解。另一方面,Collection size()操作返回的int仅仅是一个计数,这是整数的基本含义,因此没有隐含任何新概念。
Implicit concepts, recognized or unrecognized, count just as much as explicit references. Although we can generally ignore dependencies on primitive values such as integers and strings, we can’t ignore what they represent. For example, in the first paint mixing examples, the Paint object held three public integers representing red, yellow, and blue color values. The creation of the Pigment Color object did not increase the number of concepts involved or the dependencies. It did make the ones that were already there more explicit and easier to understand. On the other hand, the Collection size() operation returns an int that is simply a count, the basic meaning of an integer, so no new concept is implied.
任何依赖关系在被证明是对象概念的基础之前,都应受到质疑。这种审查始于对模型概念本身的分解。然后,它需要关注每一个单独的关联和操作。模型和设计选择会逐渐削弱依赖关系——有时甚至会将其消除。
Every dependency is suspect until proven basic to the concept behind the object. This scrutiny starts with the factoring of the model concepts themselves. Then it requires attention to each individual association and operation. Model and design choices can chip away at dependencies—often to zero.
低耦合是对象设计的基石。如果可以,就做到极致,将 所有其他概念排除在外。这样,类就完全自包含,可以单独学习和理解。每个这样的自包含类都能显著减轻理解 模块的负担。
Low coupling is fundamental to object design. When you can, go all the way. Eliminate all other concepts from the picture. Then the class will be completely self-contained and can be studied and understood alone. Every such self-contained class significantly eases the burden of understanding a MODULE.
同一模块内其他类的依赖比模块外的依赖危害更小。同样,当两个对象本身就紧密耦合时,对同一对对象进行多次操作实际上可以更清晰地阐明它们之间的关系。我们的目标不是消除所有依赖,而是消除所有非必要的依赖。如果无法消除所有依赖,那么每移除一个依赖,开发人员就可以专注于剩余的概念性依赖。
Dependencies on other classes within the same module are less harmful than those outside. Likewise, when two objects are naturally tightly coupled, multiple operations involving the same pair can actually clarify the nature of the relationship. The goal is not to eliminate all dependencies, but to eliminate all nonessential ones. If every dependency can’t be eliminated, each one that is removed frees the developer to concentrate on the remaining conceptual dependencies.
尝试将最复杂的计算分解为独立的类,或许可以通过对关联性更强的类所持有的值对象进行建模来实现。
Try to factor the most intricate computations into STANDALONE CLASSES, perhaps by modeling VALUE OBJECTS held by the more connected classes.
颜料的概念与色彩的概念有着根本的联系。但色彩,即使是颜料的色彩,也可以脱离颜料而存在。通过明确这两个概念并提炼它们之间的关系,剩余的单向关联便揭示了一些重要的信息,即颜料与色彩的关系。 计算复杂性主要体现在类中,因此可以单独进行研究和测试。
The concept of paint is fundamentally related to the concept of color. But color, even of pigment, can be considered without paint. By making these two concepts explicit and distilling the relationship, the remaining one-way association says something important, and the Pigment Color class, where most of the computational complexity lies, can be studied and tested alone.
低耦合是减少概念过载的基本方法。独立类是低耦合的极端例子。
Low coupling is a basic way to reduce conceptual overload. A STANDALONE CLASS is an extreme of low coupling.
消除依赖关系不应意味着随意地将所有东西简化为基本类型,从而降低模型的复杂度。本章的最后一个模式——操作闭包——就是一个在保持丰富接口的同时减少依赖关系的技巧示例。
Eliminating dependencies should not mean dumbing down the model by arbitrarily reducing everything to primitives. The final pattern of this chapter, CLOSURE OF OPERATIONS, is an example of a technique for reducing dependency while keeping a rich interface. . . .
如果我们取两个实数相乘,结果仍然是一个实数。[实数包括所有有理数和所有无理数。] 因为这个结论总是成立的,所以我们说实数集“在乘法运算下封闭”:没有办法离开这个集合。当集合中的任意两个元素相乘时,结果仍然包含在这个集合中。
If we take two real numbers and multiply them together, we get another real number. [The real numbers are all the rational numbers and all the irrational numbers.] Because this is always true, we say that the real numbers are “closed under the operation of multiplication”: there is no way to escape the set. When you combine any two elements of the set, the result is also included in the set.
—德雷克塞尔大学数学论坛
—The Math Forum, Drexel University
当然,依赖关系是不可避免的,如果这种依赖关系是概念的基础,那么它就不是坏事。如果接口被简化到只处理基本类型,可能会使其功能变弱。但是,很多不必要的依赖关系,甚至整个概念,都是在接口中引入的。
Of course, there will be dependencies, and that isn’t a bad thing when the dependency is fundamental to the concept. Stripping interfaces down to deal with nothing but primitives can impoverish them. But a lot of unnecessary dependencies, and even entire concepts, get introduced at interfaces.
大多数有趣的物体最终都会做出一些仅靠基本元素无法描述的事情。
Most interesting objects end up doing things that can’t be characterized by primitives alone.
精细设计中另一个常见的做法是我称之为“运算封闭性”的原则。这个名称源于最精细的概念系统——数学。例如,1 + 1 = 2。加法运算在实数集下是封闭的。数学家们非常注重避免引入无关概念,而封闭性为他们提供了一种无需引入任何其他概念即可定义运算的方法。我们如此习惯于数学的精细性,以至于很难理解它那些看似不起眼的小技巧有多么强大。但这种技巧在软件设计中也得到了广泛应用。XSLT 的基本用途是将一个 XML 文档转换为另一个 XML 文档。这种 XSLT 操作在 XML 文档集下是封闭的。封闭性极大地简化了运算的解释,也使得将封闭运算链接或组合起来变得容易。
Another common practice in refined designs is what I call “CLOSURE OF OPERATIONS.” The name comes from that most refined of conceptual systems, mathematics. 1 + 1 = 2. The addition operation is closed under the set of real numbers. Mathematicians are fanatical about not introducing extraneous concepts, and the property of closure provides them a way of defining an operation without involving any other concepts. We are so accustomed to the refinement of mathematics that it can be hard to grasp how powerful its little tricks are. But this one is used extensively in software designs as well. The basic use of XSLT is to transform one XML document into another XML document. This sort of XSLT operation is closed under the set of XML documents. The property of closure tremendously simplifies the interpretation of an operation, and it is easy to think about chaining together or combining closed operations.
所以:
Therefore:
在合适的地方,定义一个操作,其返回类型与其参数类型相同。如果实现者拥有用于计算的状态,那么实现者实际上就是该操作的参数,因此参数和返回值应与实现者类型相同。这样的操作在其类型的实例集合下是封闭的。封闭操作提供了一个高级接口,而不会引入对其他概念的任何依赖。
Where it fits, define an operation whose return type is the same as the type of its argument(s). If the implementer has state that is used in the computation, then the implementer is effectively an argument of the operation, so the argument(s) and return value should be of the same type as the implementer. Such an operation is closed under the set of instances of that type. A closed operation provides a high-level interface without introducing any dependency on other concepts.
这种模式最常应用于值对象(VALUE OBJECT)的操作。由于实体(ENTITY)的生命周期在领域中具有重要意义,因此不能随意创建一个新实体来回答问题。有些操作在实体类型下是封闭的。例如,你可以向一个员工对象查询其主管,并得到另一个员工对象。但一般来说,实体不太可能是计算的结果。因此,在大多数情况下,这方面的机会应该在值对象(VALUE OBJECT)中寻找。
This pattern is most often applied to the operations of a VALUE OBJECT. Because the life cycle of an ENTITY has significance in the domain, you can’t just conjure up a new one to answer a question. There are operations that are closed under an ENTITY type. You could ask an Employee object for its supervisor and get back another Employee. But in general, ENTITIES are not the sort of concepts that are likely to be the result of a computation. So, for the most part, this is an opportunity to look for in the VALUE OBJECTS.
运算可以对抽象类型封闭,在这种情况下,具体的参数可以属于不同的具体类。毕竟,加法运算对实数封闭,而实数可以是无理数,也可以是有理数。
An operation can be closed under an abstract type, in which case specific arguments can be of different concrete classes. After all, addition is closed under real numbers, which can be either rational or irrational.
在尝试寻找降低相互依赖性、提高内聚性的方法时,有时你会接近这种模式的一半。参数与实现者匹配,但返回类型不同;或者返回类型与接收者匹配,但参数不同。这些操作并非完全封闭,但它们确实具备一些封闭性的优势。当额外的类型是原始类型或基础库类时,它几乎能像封闭性一样解放你的思维。
As you’re experimenting, looking for ways to reduce interdependence and increase cohesion, you sometimes get halfway to this pattern. The argument matches the implementer, but the return type is different, or the return type matches the receiver and the argument is different. These operations are not closed, but they do give some of the advantages of CLOSURE. When the extra type is a primitive or basic library class, it frees the mind almost as much as CLOSURE.
在前面的例子中,“颜料颜色”操作已在 “颜料颜色”mixedWith()下封闭,本书中还有其他几个类似的例子。下面这个例子展示了即使没有达到真正的封闭状态,这个概念仍然非常有用。
In the earlier example, the Pigment Color mixedWith() operation was closed under Pigment Colors, and there are several other examples scattered through the book. Here’s an example that shows how useful this idea can be, even when true CLOSURE isn’t reached.
在 Java 中,如果要从一个集合中选择元素子集,需要请求一个迭代器。然后遍历这些元素,逐个进行测试,并将匹配项累积到一个新的集合中。
In Java, if you want to select a subset of elements from a Collection, you request an Iterator. Then you iterate through the elements, testing each one, probably accumulating the matches into a new Collection.
Set employees = (some Set of Employee objects);
Set lowPaidEmployees = new HashSet();
Iterator it = employees.iterator();
while (it.hasNext()) {
Employee anEmployee = it.next();
if (anEmployee.salary() < 40000)
lowPaidEmployees.add(anEmployee);
}
从概念上讲,我已经选择了一个集合的子集。那么,我为什么需要迭代器这个额外的概念及其复杂的机制呢?在 Smalltalk 中,我会对集合调用 `select` 操作,并将测试条件作为参数传递。返回值将是一个新的集合,其中只包含通过测试的元素。
Conceptually, I’ve selected a subset of a set. What do I need with this extra concept, Iterator, and all its mechanical complexity? In Smalltalk, I would call the “select” operation on the Collection, passing in the test as an argument. The return would be a new Collection containing just the elements that passed the test.
employees := (some Set of Employee objects).
lowPaidEmployees := employees select:
[:anEmployee | anEmployee salary < 40000].
Smalltalk集合提供了其他类似的函数,这些函数返回派生集合,这些集合可以是多个具体类的集合。这些操作并非封闭的,因为它们接受一个“代码块”作为参数。但代码块是 Smalltalk 的基本库类型,因此不会增加开发人员的认知负担。由于返回值与实现者的需求相匹配,因此可以像一系列过滤器一样将它们串联起来。它们易于编写和阅读,并且不会引入与选择子集问题无关的额外概念。
The Smalltalk Collections provide other such FUNCTIONS that return derived Collections, which can be of several concrete classes. The operations are not closed, because they take a “block” as an argument. But blocks are a basic library type in Smalltalk, so they don’t add to the developer’s mental load. Because the return value matches the implementer, they can be strung together, like a series of filters. They are easy to write and easy to read. They do not introduce extraneous concepts that are irrelevant to the problem of selecting subsets.
本章介绍的模式阐述了一种通用的设计风格和设计思路。使软件清晰易懂、可预测且易于沟通,是有效运用抽象和封装的关键。模型可以进行分解,使对象既易于使用和理解,又具备丰富的高级接口。
The patterns presented in this chapter illustrate a general style of design and a way of thinking about design. Making software obvious, predictable, and communicative makes abstraction and encapsulation effective. Models can be factored so that objects are simple to use and understand yet still have rich, high-level interfaces.
这些技术需要相当高超的设计技能才能应用,有时甚至需要编写客户端代码。模型驱动设计的有效性取决于详细设计和实现决策的质量,几个不称职的开发人员就可能导致项目偏离目标。
These techniques require fairly advanced design skills to apply and sometimes even to write a client. The usefulness of a MODEL-DRIVEN DESIGN is sensitive to the quality of the detailed design and implementation decisions, and it only takes a few confused developers to derail a project from the goal.
也就是说,对于愿意培养建模和设计技能的团队而言,这些模式及其所反映的思维方式能够产生开发人员可以反复修改以创建复杂软件的软件。
That said, for the team willing to cultivate its modeling and design skills, these patterns and the way of thinking they reflect yield software that developers can work and rework to create complex software.
即使我们采用相对非正式的测试方法,断言也能带来更好的设计。但并不能真正保证这一点。手写软件。仅举一例规避断言的方法,代码可能产生未被明确排除的额外副作用。无论我们的设计多么基于模型,最终我们仍然需要编写过程来实现概念交互的效果。而且,我们花费大量时间编写样板代码,这些代码实际上并没有增加任何意义或行为。这既繁琐又容易出错,而且大部分都会模糊我们模型的含义。(有些语言比其他语言好,但所有语言都需要我们做大量的繁琐工作。)意图揭示接口和本章中的其他模式有所帮助,但它们永远无法赋予传统的面向对象程序形式上的严谨性。
ASSERTIONS can lead to much better designs, even with our relatively informal way of testing them. But there can be no real guarantees in handwritten software. To name just one way of evading ASSERTIONS, code could have additional side effects that were not specifically excluded. No matter how MODEL-DRIVEN our design is, we still end up writing procedures to produce the effect of the conceptual interactions. And we spend so much of our time writing boilerplate code that doesn’t really add any meaning or behavior. This is tedious and fraught with error, and the bulk of it obscures the meaning of our model. (Some languages are better than others, but all require us to do a lot of grunt work.) INTENTION-REVEALING INTERFACES and the other patterns in this chapter help, but they can never give conventional object-oriented programs formal rigor.
这些是声明式设计背后的部分动机。这个术语对不同的人来说含义各异,但通常指的是一种将程序或程序的一部分编写成可执行规范的方式。对属性的精确描述实际上控制着软件。这种控制可以通过多种形式实现,例如反射机制,或者在编译时通过代码生成(根据声明自动生成常规代码)。这种方法允许其他开发人员完全按照声明的内容执行,这是一种绝对的保证。
These are some of the motivations behind declarative design. This term means many things to many people, but usually it indicates a way to write a program, or some part of a program, as a kind of executable specification. A very precise description of properties actually controls the software. In its various forms, this could be done through a reflection mechanism or at compile time through code generation (producing conventional code automatically, based on the declaration). This approach allows another developer to take the declaration at face value. It is an absolute guarantee.
从模型属性声明生成可运行程序是模型驱动设计(MDD)的终极目标,但在实践中也存在一些陷阱。例如,以下是我多次遇到的两个具体问题。
Generating a running program from a declaration of model properties is a kind of Holy Grail of MODEL-DRIVEN DESIGN, but it does have its pitfalls in practice. For example, here are just two particular problems I’ve encountered more than once.
• 这种声明语言表达能力不足,无法完成所有必要操作,而且其框架使得软件很难扩展到自动化部分之外。
• A declaration language not expressive enough to do everything needed, but a framework that makes it very difficult to extend the software beyond the automated portion
• 某些代码生成技术通过将生成的代码合并到手写代码中,破坏了迭代循环,使得代码再生变得极具破坏性。
• Code-generation techniques that cripple the iterative cycle by merging generated code into handwritten code in a way that makes regeneration very destructive
许多声明式设计的尝试都带来了意想不到的后果,那就是模型和应用程序的简化,因为开发人员受到框架限制的束缚,为了交付成果而进行设计上的简化。
The unintended consequence of many attempts at declarative design is the dumbing-down of the model and application, as developers, trapped by the limitations of the framework, enact design triage in order to get something delivered.
基于规则的编程,结合推理引擎和规则库,是另一种很有前景的声明式设计方法。然而,一些不易察觉的问题可能会破坏这种设计理念。
Rule-based programming with an inference engine and a rule base is another promising approach to declarative design. Unfortunately, subtle issues can undermine this intention.
虽然基于规则的程序在原则上是声明式的,但大多数系统都添加了“控制谓词”以进行性能调优。这些控制代码会引入副作用,使得程序的行为不再完全由声明的规则决定。添加、删除或重新排列规则可能会导致意想不到的错误结果。因此,逻辑程序员必须像面向对象程序员一样,谨慎地保持代码效果的清晰性。
Although a rules-based program is declarative in principle, most systems have “control predicates” that were added to allow performance tuning. This control code introduces side effects, so that the behavior is no longer dictated completely by the declared rules. Adding, removing, or reordering the rules can cause unexpected, incorrect results. Therefore, a logic programmer has to be careful to keep the effect of code obvious, just as an object programmer does.
许多声明式方法如果被开发者有意或无意地绕过,就会失效。这种情况尤其容易发生在系统难以使用或限制过多时。为了享受声明式编程的优势,每个人都必须遵守框架的规则。
Many declarative approaches can be corrupted if the developers bypass them intentionally or unintentionally. This is likely when the system is difficult to use or overly restrictive. Everyone has to follow the rules of the framework in order to get the benefits of a declarative program.
我见过最有价值的应用,莫过于那些功能范围狭窄的框架,它们能够自动化设计中那些特别繁琐且容易出错的环节,例如持久化和对象关系映射。优秀的框架能够帮助开发者摆脱繁重的体力劳动,同时让他们拥有充分的设计自由。
The greatest value I’ve seen delivered has been when a narrowly scoped framework automates a particularly tedious and error-prone aspect of the design, such as persistence and object-relational mapping. The best of these unburden developers of drudge work while leaving them complete freedom to design.
一种有时采用声明式方法的有趣方法是领域特定语言。在这种方法中,客户端代码使用针对特定领域特定模型定制的编程语言编写。例如,航运系统语言可能包含“货物”和“路线”等术语,以及关联这些术语的语法。然后,程序会被编译成一种传统的面向对象语言,其中类库为该语言中的术语提供了实现。
An interesting approach that is sometimes declarative is the domain-specific language. In this style, client code is written in a programming language tailored to a particular model of a particular domain. For example, a language for shipping systems might include terms such as cargo and route, along with syntax for associating them. The program is then compiled, often into a conventional object-oriented language, where a library of classes provides implementations for the terms in the language.
在这种语言中,程序可以极具表现力,并与普适语言建立最紧密的联系。这是一个令人兴奋的概念,但就我所见,基于面向对象技术的各种方法而言,领域特定语言也存在一些缺陷。
In such a language, programs can be extremely expressive, and make the strongest connection with the UBIQUITOUS LANGUAGE. This is an exciting concept, but domain-specific languages also have their drawbacks in the approaches I’ve seen based on object-oriented technology.
为了完善模型,开发者需要能够修改编程语言。这可能涉及修改语法声明和其他语言解释特性,以及修改底层类库。学习先进技术和设计理念固然重要,但我们必须冷静地评估特定团队的技能,以及未来维护团队的潜在技能。此外,使用同一种语言实现的应用程序和模型之间的无缝衔接也具有重要价值。另一个缺点是,重构客户端代码以使其符合修订后的模型及其相关的领域特定语言可能比较困难。当然,或许有人能找到解决重构问题的技术方案。
To refine the model, a developer needs to be able to modify the language. This may involve modifying grammar declarations and other language-interpreting features, as well as modifying underlying class libraries. I’m all in favor of learning advanced technology and design concepts, but we have to soberly assess the skills of a particular team, as well as the likely skills of future maintenance teams. Also, there is value in the seamlessness of an application and a model implemented in the same language. Another drawback is that it can be difficult to refactor client code to conform to a revised model and its associated domain-specific language. Of course, someone may come up with a technical fix for the refactoring problems.
这种技术可能对非常成熟的模型最为有用,尤其是在客户端代码由不同团队编写的情况下。通常,这种设置会导致技术娴熟的框架构建者和技术水平较低的应用程序构建者之间形成有害的隔阂,但情况并非必须如此。
This technique might be most useful for very mature models, perhaps where client code is being written by a different team. Generally, such setups lead to the poisonous distinction between highly technical framework builders and technically unskilled application builders, but it doesn’t have to be that way.
在 Scheme 编程语言中,非常类似的内容是标准编程风格的一部分,这样就可以在不分裂系统的情况下创建特定领域语言的表达能力。
In the scheme programming language, something very similar is part of standard programming style, so that the expressiveness of a domain-specific language can be created without bifurcating the system.
一旦你的设计具备了意图明确的接口、无副作用的函数和断言,你就开始迈入声明式设计的领域了。声明式设计的诸多优势,都来自于那些能够组合、清晰传达其含义、且具有明确或显而易见的效果(或者根本没有可观察的效果)的元素。
Once your design has INTENTION-REVEALING INTERFACES, SIDE-EFFECT-FREE FUNCTIONS, and ASSERTIONS, you are edging into declarative territory. Many of the benefits of declarative design are obtained once you have combinable elements that communicate their meaning, and have characterized or obvious effects, or no observable effects at all.
灵活的设计使得客户端代码能够采用声明式设计风格。为了说明这一点,下一节将整合本章中的一些模式,使规范更加灵活和声明化。
A supple design can make it possible for the client code to use a declarative style of design. To illustrate, the next section will bring together some of the patterns in this chapter to make the SPECIFICATION more supple and declarative.
第九章介绍了规范的基本概念、它在程序中的作用,以及实现过程中涉及的一些内容。现在,让我们来看看一些在规则复杂的情况下非常有用的附加功能。
Chapter 9 covered the basic concept of SPECIFICATION, the roles it can play in a program, and some sense of what is involved in implementation. Now let’s take a look at a few bells and whistles that can be very useful in some situations with complicated rules.
规范是对已有的形式体系——谓词——的一种改编。谓词还有其他一些有用的性质,我们可以有选择地加以利用。
SPECIFICATION is an adaptation of an established formalism, the predicate. Predicates have other useful properties that we can draw on, selectively.
使用规范时,您很快就会遇到需要组合多个规范的情况。正如前面提到的,规范是谓词的一个例子,谓词可以使用“与”、“或”和“非”运算进行组合和修改。这些逻辑运算在谓词下是封闭的,因此规范的组合将体现运算封闭性。
When using SPECIFICATIONS, you quickly come across situations in which you would like to combine them. As just mentioned, a SPECIFICATION is an example of a predicate, and predicates can be combined and modified with the operations “AND,” “OR,” and “NOT.” These logical operations are closed under predicates, so SPECIFICATION combinations will exhibit CLOSURE OF OPERATIONS.
由于规范中内置了重要的通用功能,因此创建一个可用于各种规范的抽象类或接口就非常有用。这意味着将参数类型定义为某种高级抽象类。
As significant generalized capability is built into SPECIFICATIONS, it becomes very useful to create an abstract class or interface that can be used for SPECIFICATIONS of all sorts. This means typing arguments as some high-level abstract class.
public interface Specification {
boolean isSatisfiedBy(Object candidate);
}
这种抽象需要在方法开头添加一个守卫子句,但除此之外,它不影响功能。例如,容器规范(来自第 9 章第236页的示例)将按如下方式修改:
This abstraction calls for a guard clause at the beginning of the method, but otherwise it does not affect functionality. For example, the Container Specification (from the example in Chapter 9, on page 236) would be modified this way:
public class ContainerSpecification implements Specification {
private ContainerFeature requiredFeature;
public ContainerSpecification(ContainerFeature required) {
requiredFeature = required;
}
boolean isSatisfiedBy(Object candidate){
if (!candidate instanceof Container) return false;
return
(Container)candidate.getFeatures().contains(requiredFeature);
}
}
现在,让我们通过添加以下三个新操作来扩展Specification接口:
Now, let’s extend the Specification interface by adding the three new operations:
public interface Specification {
boolean isSatisfiedBy(Object candidate);
Specification and(Specification other);
Specification or(Specification other);
Specification not();
}
请注意,某些容器规格要求使用通风容器,而另一些则要求使用装甲容器。一种既易挥发又易爆的化学品,想必需要同时满足这两种规格要求。使用新方法,这很容易实现。
Recall that some Container Specifications were configured to require ventilated Containers and others to require armored Containers. A chemical that is both volatile and explosive would, presumably, need both of these SPECIFICATIONS. Easily done, using the new methods.
Specification ventilated = new ContainerSpecification(VENTILATED);
Specification armored = new ContainerSpecification(ARMORED);
Specification both = ventilated.and(armored);
该声明定义了一个新的Specification对象,并包含了预期的属性。这种组合原本需要一个更复杂的容器 Specification,而且仍然属于特殊用途。
The declaration defines a new Specification object with the expected properties. This combination would have required a more complicated Container Specification, and would still have been special purpose.
假设我们有不止一种通风容器。对于某些物品来说,用哪种容器包装可能并不重要,它们可以放在任何一种容器中。
Suppose we had more than one kind of ventilated Container. It might not matter for some items which kind they were packed into. They could be placed in either type.
Specification ventilatedType1 =
new ContainerSpecification(VENTILATED_TYPE_1);
Specification ventilatedType2 =
new ContainerSpecification(VENTILATED_TYPE_2);
Specification either = ventilatedType1.or(ventilatedType2);
如果认为用专用容器储存沙子是一种浪费,我们可以通过指定一种没有特殊功能的“廉价”容器来禁止这种做法。
If it was considered wasteful to store sand in specialized containers, we could prohibit it by SPECIFYING a “cheap” container with no special features.
Specification cheap = (ventilated.not()).and(armored.not());
这一限制本可以防止第 9 章中讨论的原型仓库包装机的一些次优行为。
This constraint would have prevented some of the suboptimal behavior of the prototype warehouse packer discussed in Chapter 9.
利用简单元素构建复杂规范的能力增强了代码的表达能力。这些组合以声明式风格编写。
The ability to build complex specifications out of simple elements increases the expressiveness of the code. The combinations are written in a declarative style.
根据规范的具体实现方式,这些运算符的实现可能容易也可能困难。以下是一个非常简单的实现示例,在某些情况下效率低下,而在另一些情况下则非常实用。它仅作为示例进行说明。与任何模式一样,实现方式多种多样。
Depending on how SPECIFICATIONS are implemented, these operators may be easy or difficult to provide. What follows is a very simple implementation, which would be inefficient in some situations and quite practical in others. It is meant as an explanatory example. Like any pattern, there are many ways to implement it.
public abstract class AbstractSpecification implements
Specification {
public Specification and(Specification other) {
return new AndSpecification(this, other);
}
public Specification or(Specification other) {
return new OrSpecification(this, other);
}
public Specification not() {
return new NotSpecification(this);
}
}
public class AndSpecification extends AbstractSpecification {
Specification one;
Specification other;
public AndSpecification(Specification x, Specification y) {
one = x;
other = y;
}
public boolean isSatisfiedBy(Object candidate) {
return one.isSatisfiedBy(candidate) &&
other.isSatisfiedBy(candidate);
}
}
public class OrSpecification extends AbstractSpecification {
Specification one;
Specification other;
public OrSpecification(Specification x, Specification y) {
one = x;
other = y;
}
public boolean isSatisfiedBy(Object candidate) {
return one.isSatisfiedBy(candidate) ||
other.isSatisfiedBy(candidate);
}
}
public class NotSpecification extends AbstractSpecification {
Specification wrapped;
public NotSpecification(Specification x) {
wrapped = x;
}
public boolean isSatisfiedBy(Object candidate) {
return !wrapped.isSatisfiedBy(candidate);
}
}
图 10.14.规范的复合设计
Figure 10.14. COMPOSITE design of SPECIFICATION
这段代码的编写目标是尽可能便于在书中阅读。正如我所说,在某些情况下,这种写法可能效率低下。然而,还有其他实现方案可以减少对象数量、提升速度,或者与某些项目中使用的特殊技术兼容。重要的是要有一个能够捕捉领域关键概念的模型,以及一个忠实于该模型的实现。这为解决性能问题留下了很大的空间。
This code was written to be as easy as possible to read in a book. As I said, there may be situations in which this is inefficient. However, other implementation options are possible that would minimize object count or boost speed, or perhaps be compatible with idiosyncratic technologies present in some project. The important thing is a model that captures the key concepts of the domain, along with an implementation that is faithful to that model. That leaves a lot of room to solve performance problems.
此外,很多情况下并不需要如此全面的通用性。特别是,AND 运算符的使用频率远高于其他运算符,而且实现复杂度也更低。如果只需要 AND 运算符,完全可以只实现它。
Also, this full generality is not needed in many cases. In particular, AND tends to be used a lot more than the others, and it also tends to create less implementation complexity. Don’t be afraid to implement only AND, if that is all you need.
早在第二章第30页的示例对话框中,开发人员显然还没有实现其规范中的“满足于”行为。在此之前,该规范仅用于按需构建。即便如此,抽象仍然完好无损,添加功能也相对容易。使用模式并不意味着构建不需要的功能。只要概念不混淆,这些功能可以稍后添加。
Way back in Chapter 2, in the example dialog on page 30, the developers had apparently not implemented the “satisfied by” behavior of their SPECIFICATION. Up to that point, the SPECIFICATION had been used only for building to order. Even so, the abstraction was intact, and adding functionality was relatively easy. Using a pattern doesn’t mean building features you don’t need. They can be added later, as long as the concepts don’t get muddled.
有些实现环境对细粒度对象的支持并不理想。我曾经参与过一个项目,该项目使用的对象数据库坚持给每个对象分配一个对象 ID 并进行跟踪。每个对象都会占用大量内存空间和性能开销,而总地址空间也成了限制因素。我在领域设计的一些关键点采用了规范(SPECIFICATIONS),我认为这是个明智之举。但我使用了本章所述实现的略微复杂一些的版本,这无疑是个错误。它导致产生了数百万个细粒度对象,严重拖慢了系统速度。
Some implementation environments don’t accommodate very fine grained objects very well. I once worked on a project with an object database that insisted on giving an object ID to every object and then tracking it. Each object had lots of overhead in memory space and performance, and total address space was a limiting factor. I employed SPECIFICATIONS at some important points in the domain design, which I think was a good decision. But I used a slightly more elaborate version of the implementation described in this chapter, which was definitely a mistake. It resulted in millions of very fine grained objects that contributed to bogging the system down.
以下是一个替代实现的示例,它将复合规范编码为字符串或数组,以编码逻辑表达式,并在运行时进行解释。
Here is an example of an alternative implementation that encodes the composite SPECIFICATION as a string or array encoding the logical expression, to be interpreted at runtime.
(如果您不知道如何实现这一点,请不要担心。重要的是要意识到,使用逻辑运算符实现规范的方法有很多种,因此,如果简单的方法在您的实际情况下不实用,您还有其他选择。)
(Don’t worry if you do not see how you would implement this. The important thing is to realize that there are many ways of implementing a SPECIFICATION with logical operators, and so if the simple one is not practical in your situation, you have options.)
当你想测试一个候选对象时,你需要解析这个结构,这可以通过弹出每个元素,然后根据运算符的要求评估它或弹出下一个元素来实现。最终你会得到这样的结果:
When you want to test a candidate, you have to interpret this structure, which can be done by popping off each element, then evaluating it or popping off the next as required by an operator. You would end up with this:
and(not(armored), not(ventilated))
这种设计有优点(+)和缺点(-):
This design has pros (+) and cons (–):
+物体数量少
+ Low object count
+高效利用内存
+ Efficient use of memory
需要更高级的开发人员
– Requires more sophisticated developers
你必须找到一种权衡利弊后适合自身情况的实现方案。相同的模式和模型可以衍生出截然不同的实现方式。
You have to find an implementation with trade-offs that work for your circumstances. The same pattern and model can underlie very different implementations.
最后这个特性通常不是必需的,而且实现起来可能很困难,但它偶尔能解决一些非常棘手的问题。它也阐明了规范的含义。
This final feature is not usually needed and can be difficult to implement, but every now and then it solves a really hard problem. It also elucidates the meaning of a SPECIFICATION.
再以第235页示例中的化学品仓库包装工为例。回想一下,每种化学品都有其容器规格,包装 服务部门保证在将桶装入容器时,所有这些规格都将得到满足。一切都很顺利……直到有人更改了规定。
Consider again the chemical warehouse packer from the example on page 235. Recall that each Chemical had a Container Specification, and the Packer SERVICE guaranteed that all these would be satisfied when Drums are assigned to Containers. All is well... until someone changes the regulations.
每隔几个月就会发布一套新的规则,我们的用户希望能够生成一份清单,列出现在有更严格要求的化学品类型。
Every few months a new set of rules is issued, and our users would like to be able to produce a list of the chemical types that now have more stringent requirements.
当然,我们可以通过对库存中的每个油桶进行验证(并根据新的规格进行调整) ,找出所有不再符合规格的油桶,从而给出部分答案(用户可能也希望得到这个答案)。这样就能告诉用户需要移动现有库存中的哪些油桶。
Of course, we could give a partial answer (and one the users probably also want) by running a validation of each Drum in the inventory, with the new SPECIFICATIONS in place, and finding all those that no longer meet the SPEC. This would tell the users which Drums in the existing inventory they need to move.
但他们要求的是一份处理要求更加严格的化学品清单。或许公司目前没有这类化学品,或许它们只是恰好被装在了更严格的容器里。无论哪种情况,刚才提到的那份报告都不会列出这些化学品。
But what they asked for was a list of chemicals whose handling has become more stringent. Perhaps there are none in-house right now, or perhaps they just happened to be packed into a more stringent container. In either case, the report just described would not list them.
让我们引入一个新的操作,直接比较两个规范。
Let’s introduce a new operation for directly comparing two SPECIFICATIONS.
boolean subsumes(Specification other);
更严格的规范可以涵盖不太严格的规范。它可以取代不太严格的规范,而不会忽略任何先前的要求。
A more stringent SPEC subsumes a less stringent one. It could take its place without any previous requirement being neglected.
图 10.15。汽油容器的规格已更加严格。
Figure 10.15. The SPECIFICATION for a gasoline container has been tightened.
用规范的语言来说,我们会说新的规范 包含了旧的规范,因为任何满足新规范的候选方案也满足旧的规范。
In the language of SPECIFICATION, we would say that the new SPECIFICATION subsumes the old SPECIFICATION, because any candidate that would satisfy the new SPEC would also satisfy the old.
如果将这些规范中的每一个都视为谓词,则包含关系等价于逻辑蕴涵。使用常规符号,A → B表示语句A蕴含语句B,即如果A为真,则B也为真。
If each of these SPECIFICATIONS is viewed as a predicate, subsumption is equivalent to logical implication. Using conventional notation, A → B means that statement A implies statement B, so that if A is true, B is also true.
让我们把这个逻辑应用到容器匹配的需求中。当规范发生变更时,我们想知道新的规范是否满足旧规范的所有条件。
Let’s apply this logic to our container-matching needs. When a SPECIFICATION is being changed, we would like to know if the proposed new SPEC meets all the conditions of the old one.
新规格 → 旧规格
New Spec → Old Spec
也就是说,如果新规范为真,那么旧规范也为真。一般而言,证明逻辑蕴含关系非常困难,但特殊情况则相对容易。例如,特定的参数化规范可以定义自己的包含规则。
That is, if the new spec is true, then the old spec is also true. Proving a logical implication in a general way is very difficult, but special cases can be easy. For example, particular parameterized SPECS can define their own subsumption rule.
public class MinimumAgeSpecification {
int threshold;
public boolean isSatisfiedBy(Person candidate) {
return candidate.getAge() >= threshold;
}
public boolean subsumes(MinimumAgeSpecification other) {
return threshold >= other.getThreshold();
}
}
一个 JUnit 测试可能包含以下内容:
A JUnit test might contain this:
drivingAge = new MinimumAgeSpecification(16);
votingAge = new MinimumAgeSpecification(18);
assertTrue(votingAge.subsumes(drivingAge));
另一个适合解决容器规范问题的实际特例是SPECIFICATION接口,它将包含关系与单个逻辑运算符 AND 结合起来。
Another practical special case, one suited to address the Container Specification problem, is a SPECIFICATION interface combining subsumption with the single logical operator AND.
public interface Specification {
boolean isSatisfiedBy(Object candidate);
Specification and(Specification other);
boolean subsumes(Specification other);
}
仅使用 AND 运算符证明蕴含关系很简单:
Proving implication with only the AND operator is simple:
A和B → A
A AND B → A
或者,在更复杂的情况下:
or, in a more complicated case:
A和B和 C → A和B
A AND B AND C → A AND B
因此,如果复合规范能够收集所有“AND”组合在一起的叶子规范,那么我们所要做的就是检查包含规范是否拥有被包含规范的所有叶子,以及一些额外的叶子——它的叶子是另一个规范叶子集的超集。
So if the Composite Specification is able to collect all the leaf SPECIFICATIONS that are “ANDed” together, then all we have to do is check that the subsuming SPECIFICATION has all the leaves that the subsumed one has, and maybe some extra ones as well—its leaves are a superset of the other SPEC’s set of leaves.
public boolean subsumes(Specification other) {
if (other instanceof CompositeSpecification) {
Collection otherLeaves =
(CompositeSpecification) other.leafSpecifications();
Iterator it = otherLeaves.iterator();
while (it.hasNext()) {
if (!leafSpecifications().contains(it.next()))
return false;
}
} else {
if (!leafSpecifications().contains(other))
return false;
}
return true;
}
这种交互方式可以进一步改进,以便比较精心选择的参数化叶子节点规范以及其他一些复杂情况。遗憾的是,当包含 OR 和 NOT 运算符时,这些证明会变得更加复杂。在大多数情况下,最好通过做出选择来避免这种复杂性,要么放弃某些运算符,要么放弃包含关系。如果两者都需要,则应仔细考虑其带来的收益是否足以抵消所造成的困难。
This interaction could be enhanced to compare carefully chosen parameterized leaf SPECIFICATIONS and some other complications. Unfortunately, when OR and NOT are included, these proofs become much more involved. In most situations it is best to avoid such complexity by making a choice, either forgoing some of the operators or forgoing subsumption. If both are needed, consider carefully if the benefit is great enough to justify the difficulty.
本章介绍了一系列用于明确代码意图、使代码使用后果透明化以及解耦模型元素的技术。即便如此,这种设计仍然十分困难。你不能只是看着一个庞大的系统说:“让我们让它更灵活。”你必须选择目标。以下是一些大致的方向。首先介绍几种方法,然后通过一个扩展示例来说明如何将这些模式组合在一起,并用于实现更大的设计。
This chapter has presented a raft of techniques to clarify the intent of code, to make the consequences of using it transparent, and to decouple model elements. Even so, this kind of design is difficult. You can’t just look at an enormous system and say, “Let’s make this supple.” You have to choose targets. Here are a couple of broad approaches, followed by an extended example showing how the patterns are fit together and used to take on a bigger design.
你不可能一下子就搞定整个设计。要循序渐进。系统的某些方面会给你一些启发,你可以把它们提取出来,再进行完善。你可能会发现模型中的一部分可以看作是专门的数学运算;那就把它分离出来。你的应用程序强制执行限制状态变更的复杂规则;把这些规则提取到一个单独的模型或简单的框架中,以便你声明这些规则。每完成一步,不仅新模块会更简洁,剩下的部分也会变得更小更清晰。剩下的部分可以用声明式风格编写,用特殊的数学运算或验证框架进行声明,或者用子域所采用的任何形式来编写。
You just can’t tackle the whole design at once. Pick away at it. Some aspects of the system will suggest approaches to you, and they can be factored out and worked over. You may see a part of the model that can be viewed as specialized math; separate that. Your application enforces complex rules restricting state changes; pull this out into a separate model or simple framework that lets you declare the rules. With each such step, not only is the new module clean, but also the part left behind is smaller and clearer. Part of what’s left is written in a declarative style, a declaration in terms of the special math or validation framework, or whatever form the subdomain takes.
与其分散精力,不如集中精力在一个领域取得显著成效,使设计的一部分真正灵活。第15章将更深入地探讨如何选择和管理子域。
It is more useful to make a big impact on one area, making a part of the design really supple, than to spread your efforts thin. Chapter 15 discusses in more depth how to choose and manage subdomains.
从零开始构建一个严谨的概念框架并非易事。有时,你会在项目生命周期中发现并完善现有的框架。但你通常可以借鉴并调整你所在领域或其他领域中早已建立的概念体系,其中一些体系甚至经过数百年的不断完善和提炼。例如,许多商业应用都涉及会计。会计定义了一套完善的实体和规则,这使得它能够轻松地适应深度模型和灵活的设计。
Creating a tight conceptual framework from scratch is something you can’t do every day. Sometimes you discover and refine one of these over the course of the life of a project. But you can often use and adapt conceptual systems that are long established in your domain or others, some of which have been refined and distilled over centuries. Many business applications involve accounting, for example. Accounting defines a well-developed set of ENTITIES and rules that make for an easy adaptation to a deep model and a supple design.
这类形式化的概念框架有很多,但我个人最喜欢的是数学。令人惊讶的是,对基础算术进行一些巧妙的运用竟如此有用。许多领域都以某种方式运用了数学。去寻找它,去挖掘它。专门的数学概念简洁明了,可以通过清晰的规则进行组合,而且人们很容易理解。我过去研究过的“共享数学”就是一个例子,本章将以此作为结尾。
There are many such formalized conceptual frameworks, but my personal favorite is math. It is surprising how useful it can be to pull out some twist on basic arithmetic. Many domains include math somewhere. Look for it. Dig it out. Specialized math is clean, combinable by clear rules, and people find it easy to understand. One example from my past is “Shares Math,” which will end this chapter.
第八章讲述了一个构建银团贷款系统的项目中取得的突破性进展。现在,本例将详细阐述,重点介绍与该项目设计方案类似的一个设计特点。
Chapter 8 told the story of a model breakthrough on a project to build a syndicated loan system. Now this example will go into detail, focusing on just one feature of a design comparable to the one on that project.
该申请的一项要求是,当借款人偿还本金时,默认情况下,款项将根据贷款人在贷款中的份额按比例分配。
One requirement of that application was that when the borrower makes a principal payment, the money is, by default, prorated according to the lenders’ shares in the loan.
随着我们不断重构,这段代码会变得更容易理解,所以不要局限于这个版本。
As we refactor it, this code will get easier to understand, so don’t get stuck on this version.
图 10.16
Figure 10.16
public class Loan {
private Map shares;
//Accessors, constructors, and very simple methods are excluded
public Map distributePrincipalPayment(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object owner = it.next();
double initialLoanShareAmount = getShareAmount(owner);
double paymentShareAmount =
initialLoanShareAmount / total * paymentAmount;
Share paymentShare =
new Share(owner, paymentShareAmount);
paymentShares.put(owner, paymentShare);
double newLoanShareAmount =
initialLoanShareAmount - paymentShareAmount;
Share newLoanShare =
new Share(owner, newLoanShareAmount);
loanShares.put(owner, newLoanShare);
}
return paymentShares;
}
public double getAmount() {
Map loanShares = getShares();
double total = 0.0;
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Share loanShare = (Share) loanShares.get(it.next());
total = total + loanShare.getAmount();
}
return total;
}
}
这个设计已经具备了能够揭示意图的接口。但是,该distributePaymentPrincipal()方法存在一个危险之处:它既计算了分配份额,又修改了贷款。让我们重构一下,将查询与修改操作分开。
This design already has INTENTION-REVEALING INTERFACES. But the distributePaymentPrincipal() method does a dangerous thing: It calculates the shares for distribution and also modifies the Loan. Let’s refactor to separate the query from the modifier.
图 10.17
Figure 10.17
public void applyPrincipalPaymentShares(Map paymentShares) {
Map loanShares = getShares();
Iterator it = paymentShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
Share paymentShare = (Share) paymentShares.get(lender);
Share loanShare = (Share) loanShares.get(lender);
double newLoanShareAmount = loanShare.getAmount() -
paymentShare.getAmount();
Share newLoanShare = new Share(lender, newLoanShareAmount);
loanShares.put(lender, newLoanShare);
}
}
public Map calculatePrincipalPaymentShares(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
Share loanShare = (Share) loanShares.get(lender);
double paymentShareAmount =
loanShare.getAmount() / total * paymentAmount;
Share paymentShare = new Share(lender, paymentShareAmount);
paymentShares.put(lender, paymentShare);
}
return paymentShares;
}
客户端代码现在看起来像这样:
Client code now looks like this:
Map distribution =
aLoan.calculatePrincipalPaymentShares(paymentAmount);
aLoan.applyPrincipalPaymentShares(distribution);
还不错。函数已经将很多复杂性封装在了意图揭示型接口applyDrawdown()背后。但是,当我们添加`, ` 等等时,代码量就开始成倍增长calculateFeePaymentShares()。每次扩展都会使代码更加复杂和臃肿。这可能是粒度过粗的问题。传统的做法是将计算方法分解成子程序。这或许是一个不错的步骤,但我们最终想要看到底层概念边界并深化模型。具有这种概念轮廓粒度的设计元素可以组合起来,产生所需的各种变体。
Not too bad. The FUNCTIONS have encapsulated a lot of complexity behind INTENTION-REVEALING INTERFACES. But the code does begin to multiply some when we add applyDrawdown(), calculateFeePaymentShares(), and so on. Each extension complicates the code and weighs it down. This might be a point where the granularity is too coarse. The conventional approach would be to break the calculation methods down into subroutines. That could well be a good step along the way, but we ultimately want to see the underlying conceptual boundaries and deepen the model. The elements of a design with such a CONCEPT-CONTOURING grain could be combined to produce the needed variations.
现在有足够的线索可以开始探测这个新模型了。在这个实现中, Share对象是被动的,它们是……股票交易往往以复杂且底层的方式进行操作。这是因为大多数关于股票的规则和计算并非针对单个股票,而是针对股票组合。这里缺失了一个概念:股票是相互关联的,它们共同构成一个整体。明确这一概念将使我们能够更简洁地表达这些规则和计算。
There are enough pointers now to start probing for that new model. The Share objects are passive in this implementation, and they are being manipulated in complex, low-level ways. This is because most of the rules and calculations about shares don’t apply to single shares, but to groups of them. There is a missing concept: shares are related to each other as parts making up a whole. Making this concept explicit will let us express those rules and calculations more succinctly.
图 10.18
Figure 10.18
“份额饼图”代表特定贷款的总分配情况。它是一个实体,其身份在贷款总额中是局部的。实际的分配计算可以委托给“份额饼图”进行。
The Share Pie represents the total distribution of a specific Loan. It is an ENTITY whose identity is local within the AGGREGATE of the Loan. The actual distribution calculations can be delegated to the Share Pie.
图 10.19
Figure 10.19
public class Loan {
private SharePie shares;
//Accessors, constructors, and straightforward methods
//are omitted
public Map calculatePrincipalPaymentDistribution(
double paymentAmount) {
return getShares().prorated(paymentAmount);
}
public void applyPrincipalPayment(Map paymentShares) {
shares.decrease(paymentShares);
}
}
贷款流程已简化,份额计算也集中在一个专门负责该项职责的价值对象中。然而,这些计算并没有真正变得更灵活或更易于使用。
The Loan is simplified, and the Share calculations are centralized in a VALUE OBJECT focused on that responsibility. Still, the calculations haven’t really become more versatile or easier to use.
通常,实践新设计的过程会带来对模型本身的新见解。在本例中,“贷款”和“股份”板块的紧密耦合似乎掩盖了“股份”板块与“股份”之间的关系。如果我们把“股份”板块设为值对象会发生什么?
Often, the hands-on experience of implementing a new design will trigger a new insight into the model itself. In this case, the tight coupling of the Loan and Share Pie seems to be obscuring the relationship of the Share Pie and the Shares. What would happen if we made Share Pie a VALUE OBJECT?
这意味着上述操作increase(Map)将decrease(Map)不被允许,因为共享饼图必须是不可变的。要更改共享饼图的值,必须替换整个饼图。因此,您可以执行诸如 `$($($($($($($($($($($($($($($($($(($(($( ( ) ...addShares(Map)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
This would mean that increase(Map) and decrease(Map) would not be allowed, because the Share Pie would have to be immutable. To change the Share Pie’s value, the whole Pie would have to be replaced. So you could have operations such as addShares(Map) that would return a whole new, larger Share Pie.
让我们彻底结束运营。与其“增加”一个股份饼图或向其中添加股份,不如将两个股份饼图相加:结果就是新的、更大的股份饼图。
Let’s go all the way to CLOSURE OF OPERATIONS. Instead of “increasing” a Share Pie or adding Shares to it, just add two Share Pies together: the result is the new, larger Share Pie.
我们只需更改返回类型即可部分关闭prorate()对Share Pie 的prorated()操作。将其重命名以强调其无副作用。“Shares Math”开始成形,最初包含四个操作。
We can partially close the prorate() operation over Share Pie just by changing the return type. Renaming it to prorated() emphasizes its lack of side effects. “Shares Math” starts to take shape, initially with four operations.
图 10.20
Figure 10.20
我们可以对新的值对象(即份额饼图)做出一些明确的断言。每种方法都有其特定的含义。
We can make some well-defined ASSERTIONS about our new VALUE OBJECTS, the Share Pies. Each method means something.
public class SharePie {
private Map shares = new HashMap();
//Accessors and other straightforward methods are omitted
public double getAmount() {
double total = 0.0;
Iterator it = shares.keySet().iterator();
while(it.hasNext()) { The whole is equal to the
Share loanShare = getShare(it.next()); sum of its parts.
total = total + loanShare.getAmount();
}
return total;
}
public SharePie minus(SharePie otherShares) {
SharePie result = new SharePie();
Set owners = new HashSet();
owners.addAll(getOwners());
owners.addAll(otherShares.getOwners()); The difference between
Iterator it = owners.iterator(); two Pies is the difference
while(it.hasNext()) { between each owner's
Object owner = it.next(); share.
double resultShareAmount = getShareAmount(owner) –
otherShares.getShareAmount(owner);
result.add(owner, resultShareAmount);
}
return result;
}
public SharePie plus(SharePie otherShares) { The combination of two
//Similar to implementation of minus() Pies is the combination of
} each owner's share.
public SharePie prorated(double amountToProrate) {
SharePie proration = new SharePie();
double basis = getAmount(); An amount can be divided
Iterator it = shares.keySet().iterator(); proportionately
while(it.hasNext()) { among all shareholders.
Object owner = it.next();
Share share = getShare(owner);
double proratedShareAmount =
share.getAmount() / basis * amountToProrate;
proration.add(owner, proratedShareAmount);
}
return proration;
}
}
此时,至关重要的Loan类中的方法可以非常简单,如下所示:
At this point, the methods in the all-important Loan class could be as simple as this:
public class Loan {
private SharePie shares;
//Accessors, constructors, and straightforward methods
//are omitted
public SharePie calculatePrincipalPaymentDistribution(
double paymentAmount) {
return shares.prorated(paymentAmount);
}
public void applyPrincipalPayment(SharePie paymentShares) {
setShares(shares.minus(paymentShares));
}
这些简短的方法都明确说明了其含义。支付本金是指从贷款中逐份扣除还款额。分配本金则是将金额按比例分配给所有股东。“股份饼图”的设计使我们能够在贷款代码中使用声明式风格,从而生成更像业务交易概念定义而非计算的代码。
Each of these short methods states its meaning. Applying a principal payment means that you subtract the payment from the loan, share by share. Distributing a principal payment is done by dividing the amount pro rata among the shareholders. The design of the Share Pie has allowed us to use a declarative style in the Loan code, producing code that begins to read like a conceptual definition of the business transaction, rather than a calculation.
其他交易类型(以前过于复杂,无法一一列举)现在可以轻松申报。例如,贷款提取金额会根据各贷款人在贷款额度中的份额进行分配。新增提取金额将计入未偿贷款余额。用我们新的领域语言来说:
Other transaction types (too complicated to list before) can be declared easily now. For example, loan drawdowns are divided among lenders based on their shares of the Facility. The new draw-down is added to the outstanding Loan. In our new domain language:
public class Facility {
private SharePie shares;
. . .
public SharePie calculateDrawdownDefaultDistribution(
double drawdownAmount) {
return shares.prorated(drawdownAmount);
}
}
public class Loan {
. . .
public void applyDrawdown(SharePie drawdownShares) {
setShares(shares.plus(drawdownShares));
}
}
要了解每位贷款人与其约定出资额的偏差,请取未偿贷款金额的理论分配额,并从贷款的实际份额中减去该理论分配额:
To see the deviation of each lender from its agreed contribution, take the theoretical distribution of the outstanding Loan amount and subtract it from the Loan’s actual shares:
SharePie originalAgreement =
aFacility.getShares().prorated(aLoan.getAmount());
SharePie actual = aLoan.getShares();
SharePie deviation = actual.minus(originalAgreement);
Share Pie设计的某些特性使得代码中的重组和通信变得容易。
Certain characteristics of the Share Pie design make for this easy recombination and communication in the code.
•复杂的逻辑封装在具有无副作用函数的专用值对象中。大多数复杂逻辑都封装在这些不可变对象中。由于共享饼图是值对象,因此数学运算可以创建新实例,我们可以自由地使用这些新实例来替换过时的实例。
• Complex logic is encapsulated in specialized VALUE OBJECTS with SIDE-EFFECT-FREE FUNCTIONS. Most complex logic has been encapsulated in these immutable objects. Because Share Pies are VALUE OBJECTS, the math operations can create new instances, which we can use freely to replace outdated instances.
Share Pie 的所有方法都不会对任何现有对象造成任何更改。这使得我们可以在中间计算中自由地使用 ` plus()share_pie`、minus()`share_pie` 和 ` prorated()share_pie`,并将它们组合起来,期望它们执行其名称所暗示的操作,仅此而已。这也允许我们基于相同的方法构建分析特征。(以前,这些方法只能在实际进行分配时调用,因为每次调用后数据都会发生变化。)
None of the Share Pie methods causes any change to any existing object. This allows us to use plus(), minus(), and prorated() freely in intermediate calculations, combining them, expecting them to do what their names suggest, and nothing more. It also allows us to build analytical features based on the same methods. (Before, they could be called only when an actual distribution was made, because the data would change after each call.)
•状态修改操作简单,并以断言 (ASSERTION)为特征。Shares Math 的高级抽象允许以声明式的方式简洁地编写事务的不变量。样式。例如,偏差是实际饼图减去根据融资份额饼图按比例计算的贷款金额。
• State-modifying operations are simple and characterized with ASSERTIONS. The high-level abstractions of Shares Math allow invariants of transactions to be written concisely in a declarative style. For example, the deviation is the actual pie minus the Loan amount prorated based on the Facility’s Share Pie.
模型概念是解耦的;操作与其他类型的交互极少。Share Pie中的一些方法体现了操作的封闭性(例如,在Share Pie中,加法和减法方法是封闭的)。其他方法则接受简单的数值作为参数或返回值;它们并非封闭的,但对概念的复杂性影响甚微。Share Pie仅与另一个类Share密切交互。因此,Share Pie是自包含的、易于理解的、易于测试的,并且易于组合以形成声明式事务。这些特性继承自数学形式主义。
• Model concepts are decoupled; operations entangle a minimum of other types. Some methods on Share Pie exhibit CLOSURE OF OPERATIONS (the methods to add or subtract are closed under Share Pies). Others take simple amounts as arguments or return values; they are not closed, but they add little to the conceptual load. The Share Pie interacts closely with only one other class, Share. As a result, the Share Pie is self-contained, easily understood, easily tested, and easily combined to form declarative transactions. These properties were inherited from the math formalism.
•熟悉的公式使得协议易于理解。基于金融术语,完全可以设计出一套全新的股票操纵协议。原则上,这套协议可以灵活运用。但它有两个缺点。首先,它需要被发明出来,这是一项艰巨且充满不确定性的任务。其次,每个使用它的人都必须学习它。而“股票数学”系统让用户能够识别出他们已经熟悉的系统,并且由于其设计严格遵循算术规则,因此不会被误导。
• Familiar formalism makes the protocol easy to grasp. A wholly original protocol for manipulating shares could have been devised based on financial terminology. In principle, it could have been made supple. But it would have had two disadvantages. First, it would have to be invented, a difficult and uncertain task. Second, it would have to be learned by each person who dealt with it. People who see Shares Math recognize a system they already know, and because the design has been kept carefully consistent with the rules of arithmetic, those people are not misled.
通过提取问题中与数学形式主义相对应的部分,我们得到了一个灵活的股份系统设计,该设计进一步简化了核心的贷款和融资方法。(有关核心领域的讨论,请参见第15章。)
Pulling out the part of the problem that corresponded to the formalism of math, we arrived at a supple design for Shares that further distills the core Loan and Facility methods. (See Chapter 15 for discussion of the CORE DOMAIN.)
柔性设计对软件应对变化和复杂性的能力有着深远的影响。正如本章示例所示,它通常取决于非常细致的建模和设计决策。其影响甚至超越了具体的建模和设计问题。第15章将探讨柔性设计的战略价值,它是提炼领域模型、使大型复杂项目更易于管理的多种工具之一。
Supple design has a profound effect on the ability of software to cope with change and complexity. As the examples in this chapter have shown, it often hinges on quite detailed modeling and design decisions. The impact can go beyond a specific modeling and design problem. Chapter 15 will discuss the strategic value of supple design as one of several tools for distilling a domain model to make large and complex projects more tractable.
深度模型和灵活设计并非一蹴而就。进步源于对领域的深入学习、广泛的交流以及大量的尝试和纠错。不过,有时我们也能获得一些先机。
Deep models and supple designs don’t come easily. Progress comes from lots of learning about the domain, lots of talking, and lots of trial and error. Sometimes, though, we can get a leg up.
当一位经验丰富的开发人员遇到某个领域问题时,如果发现其中涉及熟悉的职责或关系网络,他/她就可以回忆起之前是如何解决该问题的。之前尝试过哪些模型?哪些模型奏效了?实施过程中遇到了哪些困难?又是如何解决的?之前的经验教训突然间就适用于新的情况了。其中一些模式已被记录并分享,使我们其他人也能借鉴这些积累的经验。
When an experienced developer looking at a domain problem sees a familiar sort of responsibility or a familiar web of relationships, he or she can draw on the memory of how the problem was solved before. What models were tried and which worked? What difficulties arose in implementation and how were they resolved? The trial and error of that earlier experience is suddenly relevant to the new situation. Some of these patterns have been documented and shared, allowing the rest of us to draw on the accumulated experience.
与第二部分介绍的基本构建模块模式和第十章介绍的灵活设计原则不同,这些模式层次更高、更专业,使用少量对象即可表示某些概念。它们使我们能够省去昂贵的试错过程,直接从一个已经具有表现力和可实现性的模型入手,并解决那些可能需要花费大量成本才能掌握的细微之处。从这个起点出发,我们可以进行重构和实验。这些并非现成的解决方案。
In contrast to the fundamental building block patterns presented in Part II, and the supple design principles of Chapter 10, these patterns are higher level and more specialized, involving the use of a few objects to represent some concept. They let us cut through expensive trial and error to start with a model that is already expressive and implementable and addresses subtleties that might be costly to learn. From that starting point, we refactor and experiment. These are not out-of-the-box solutions.
在《分析模式:可重用对象模型》一书中,Martin Fowler 这样定义了他的模式:
In Analysis Patterns: Reusable Object Models, Martin Fowler defined his patterns this way:
分析模式是一组概念,代表业务建模中的通用结构。它可能只与一个领域相关,也可能跨越多个领域。[ Fowler 1997,第 8 页]
Analysis patterns are groups of concepts that represent a common construction in business modeling. It may be relevant to only one domain or it may span many domains. [Fowler 1997, p. 8]
福勒提出的分析模式源于实践经验,因此在合适的场景下非常实用。这些模式为身处复杂领域的开发者提供了宝贵的迭代开发起点。其名称强调了它们的概念性本质。分析模式并非技术解决方案,而是指导开发者在特定领域构建模型的指南。
The analysis patterns Fowler presents arose from experience in the field, and so they are practical, in the right situation. Such patterns provide someone facing a challenging domain with very valuable starting points for their iterative development process. The name emphasizes their conceptual nature. Analysis patterns are not technological solutions; they are guides to help you work out a model in a particular domain.
遗憾的是,名称并未体现出这些模式中包含大量关于实现方式的讨论,甚至包括一些代码示例。福勒深知,如果只进行分析而不考虑实际设计,将会面临诸多陷阱。以下是一个有趣的例子,他甚至超越了部署层面,探讨了特定模型选择对系统长期现场维护的影响:
What the name unfortunately does not convey is that there is significant discussion of implementation in these patterns, including some code. Fowler understands the pitfalls of analysis without thought for practical design. Here is an interesting example where he is looking even beyond deployment, to the implications of specific model choices on the long-term maintenance of the system in the field:
当我们构建新的会计实务时,我们会创建一系列新的记账规则实例。我们可以在系统仍在运行的情况下,无需重新编译或重建系统即可完成此操作。虽然偶尔会遇到需要新的记账规则子类型的情况,但这种情况很少见。[第151页]
When we build a new [accounting] practice, we create a network of new instances of the posting rule. We can do this without any recompilation or rebuilding of the system, while it is still up and running. There will be unavoidable occasions when we need a new subtype of posting rule, but these will be rare. [p. 151]
在一个成熟的项目中,模型选择通常取决于应用经验。各种组件的多种实现方式都经过了尝试。其中一些已经投入生产环境,甚至经历了维护阶段。有了这些经验,许多问题都可以避免。优秀的分析模式能够借鉴其他项目的经验,将模型洞察与对设计方向和实现后果的深入探讨相结合。脱离这种背景讨论模型理念,会使它们更难应用,并有可能造成分析与设计之间致命的鸿沟,这与模型驱动设计(MDD)的理念背道而驰。
On a mature project, model choices are often informed by experience with the application. Multiple implementations of various components will have been tried. Some of these will have been carried into production and even will have faced the maintenance phase. Many problems can be avoided when such experience is available. Analysis patterns at their best can carry that kind of experience from other projects, combining model insights with extensive discussions of design directions and implementation consequences. To discuss model ideas out of that context makes them harder to apply and risks opening the deadly divide between analysis and design, which is antithetical to MODEL-DRIVEN DESIGN.
分析模式的原理和应用用实例解释比抽象描述更有效。本章将提供两个开发人员使用Fowler 1997年著作“库存和会计”章节中少量代表性模型的示例。分析模式的概述将仅限于支持示例所需的内容。显然,这并非旨在对这类模式进行全面罗列,甚至也不是要对其进行完整解释。示例模式。目的是为了说明它们如何融入领域驱动设计流程。
The principle and application of analysis patterns can be explained better by example than through abstract description. In this chapter, I will give two examples of developers making use of a small, representative sample of models from the chapter “Inventory and Accounting” in Fowler 1997. The analysis patterns will be summarized just enough to support the examples. This is obviously not an attempt to catalog patterns of this kind or even to fully explain the sample patterns. The point is to illustrate their integration into the domain-driven design process.
第十章展示了开发人员为特定专业会计应用程序寻找更深层次模型的几种可能方法。这里是另一种场景。这一次,开发人员将从福勒的《分析模式》一书中寻找有用的思路。
Chapter 10 showed various possible ways that a developer might search for a deeper model for a particular specialty accounting application. Here is yet another scenario. This time, the developers will mine Fowler’s Analysis Patterns book for useful ideas.
回顾一下,一个用于追踪贷款和其他计息资产的应用程序会计算产生的利息和费用,并追踪借款人的还款情况。一个夜间批量处理程序会将这些数据传递给原有的会计系统,并指定每笔款项应记入的具体账簿。这种设计虽然可行,但使用起来很不方便,修改起来也很棘手,而且沟通不畅。
To review, an application for tracking loans and other interest-bearing assets calculates the interest and fees generated and tracks payments from the borrower. A nightly batch process takes those figures and passes them to the legacy accounting system, indicating the specific ledger each amount should be posted to. The design works, but it is awkward to use, tricky to change, and does not communicate well.
图 11.1. 初始类图
Figure 11.1. The initial class diagram
开发人员决定阅读《分析模式》第六章“库存和会计”。以下是她认为最相关的部分摘要。
The developer decides to read Chapter 6 in Analysis Patterns, “Inventory and Accounting.” Here is a summary of the part she found most relevant.
分析模式中的会计模型
Accounting Models in Analysis Patterns
各种商业应用都会追踪账户,这些账户存放着有价值的东西,通常是金钱。在许多应用中,仅仅追踪账户余额是不够的。必须记录并控制该余额的每一次变动。这正是最基本的会计模型的根本目的。
Business applications of all sorts track accounts, which hold things of value, typically money. In a lot of applications, it isn’t enough to keep track of the amount in an account. It is essential to account for and control each change to that amount. That is the motivation for the most basic of the accounting models.
图 11.2. 基本会计模型
Figure 11.2. A basic accounting model
可以通过插入条目来增加值。可以通过插入负值条目来移除值。条目永远不会被移除,因此完整的历史记录都会被保留。余额是所有条目的综合结果。该余额可以按需计算或缓存,这一实现决策由账户接口封装。
Value can be added by inserting an Entry. Value can be removed by inserting a negative Entry. Entries are never removed, so the whole history is retained. The balance is the combined effect of all Entries. This balance could be computed on demand or cached, an implementation decision that is encapsulated by the Account interface.
会计的基本原则之一是守恒。金钱不会凭空出现,也不会无缘无故消失。它只会从一个账户转移到另一个账户。
A basic principle of accounting is conservation. Money doesn’t appear out of nowhere, nor does it disappear without a trace. It is only moved from one Account to another.
图 11.3. 交易模型
Figure 11.3. A transaction model
这就是复式记账法的基本概念:每一笔贷方支出都有对应的借方支出。当然,与其他守恒原则一样,它只适用于封闭系统,即包含所有资金来源和资金汇的系统。许多简单的应用并不需要如此严谨。
This is the well-established concept of double-entry book-keeping: Every credit has a matching debit. Of course, like other conservation principles, it applies only to a closed system, one that includes all sources and sinks. Many simple applications do not require this rigor.
福勒在他的书中包含了这些模型的更详细形式,并对权衡取舍进行了大量讨论。
In his book, Fowler includes more elaborate forms of these models and considerable discussion of the trade-offs.
这次阅读给开发人员(开发人员 1)带来了几个新思路。她把这一章的内容展示给一位同事(开发人员 2),这位同事之前和她一起研究过一些利息计算逻辑,并且编写了夜间批处理程序。他们一起粗略地修改了模型,将他们读到的一些模型元素融入其中。
This reading gives the developer (Developer 1) several new ideas. She shows the chapter to a colleague (Developer 2) who has been working on some of the interest calculation logic with her and who wrote the nightly batch program. Together, they rough out a change to their model, incorporating some of the model elements they’ve read about.
图 11.4. 新模型提案
Figure 11.4. The new model proposal
Then they pull in their domain expert (Expert) for a discussion of their new model ideas.
开发人员 1:使用这种新模型,我们会在利息账户中记入一笔利息收入,而不仅仅是调整应付利息金额。然后,再记入一笔支付款项的账目以进行平衡。
Developer 1: With this new model, we make an Entry into the Interest Account for the interest earned, rather than just adjusting the interestDueAmount. Then, another Entry for the payment balances it out.
专家:所以现在我们可以看到所有利息累积的历史记录以及还款历史记录?这正是我们一直想要的。
Expert: So now we’d be able to see a history of all the interest accruals as well as the payment history? That’s something we’ve been wanting.
开发人员2:我不确定我们对“交易”一词的使用是否完全正确。它的定义是指将资金从一个账户转移到另一个账户,而不是在同一个账户中进行两笔相互平衡的账目。
Developer 2: I’m not sure we’ve used “Transaction” quite right. The definition talks about moving money from one Account to another, not two entries that balance each other in the same Account.
开发者1:你说得对。我也担心这本书似乎特别强调交易是一次性创建的,这样一来,利息支付可能会延迟好几天。
Developer 1: That’s a good point. I was also worried that the book seems to make quite a point about the transaction being created all at once. The interest payments can be several days late.
专家:这些款项未必算逾期。他们的付款时间非常灵活。
Expert: Those payments aren’t necessarily late. There is a lot of flexibility in when they pay.
开发者 1:看来这条路走不通了。我原本以为我们可能发现了一些隐含的概念。让利息计算器创建条目对象似乎确实能更好地传达信息。而交易对象似乎能巧妙地将计算出的利息与付款联系起来。
Developer 1: So this may be a blind alley. I was thinking we might have identified some implicit concepts. Having the Interest Calculator create Entry objects does seem to communicate better. And Transaction seemed to neatly tie together the calculated interest with the payment.
专家:为什么我们需要将应计项目与付款关联起来?它们在会计系统中是分开的记账。账户余额才是关键。结合各个分录,我们就已经掌握了所需的信息。
Expert: Why do we need to tie together the accrual to the payment? They are separate postings in the accounting system. The balance on the Account is the main thing. Along with the individual Entries, we really have what we need.
开发者2:你的意思是说,你们不追踪他们是否支付了利息吗?
Developer 2: You mean you don’t track whether they’ve made the interest payment?
专家:当然,我们有。但这并不像你们这种一次性累积/一次性支付的方案那么简单。
Expert: Well, of course we do. But it isn’t as simple as this one-accrual/one-payment scheme of yours.
开发者 2:实际上,如果不再担心这种连接,很多事情都会变得简单得多。
Developer 2: It could actually simplify a lot of things to stop worrying about that connection.
开发人员1:好的,这样可以吗?[复制旧的类图并开始绘制修改草图] 对了,你刚才用了几次“应计”这个词。你能解释一下它的意思吗?
Developer 1: OK, how about this? [Takes copy of old class diagram and starts sketching modifications] By the way, you used the word accruals a few times. Could you clarify what it means?
专家:当然。权责发生制是指在费用或收入发生时就进行核算,而不管实际的资金交割时间。例如,我们每天都会累积利息,但到了月底(举例来说),我们才会收到相应的款项。
Expert: Sure. An accrual is just when you account for an expense or income at the time it is incurred, never mind when money actually changes hands. So, we accrue interest every day, but at the end of the month (for example) we receive a payment against it.
开发人员1:是的,我们确实需要这样一个词。好的,这样看起来怎么样?
Developer 1: Yes, we really needed a word like that. OK, how does this look?
图 11.5. 原始类图,应计项目与支付项目分开
Figure 11.5. Original class diagram, accruals separated from payment
开发者 1:现在我们可以消除计算器中与付款相关的所有复杂性,并且我们引入了“应计”一词,这更好地揭示了意图。
Developer 1: Now we can get rid of all the complications that were in the calculator from relating payments, and we’ve introduced the term accruals, which reveals the intent better.
专家:所以我们不会有“账户”对象了吗?我原本期待能够把所有信息集中在一起,包括应计项目、付款和余额。
Expert: So we’re not going to have the Account object? I was looking forward to being able to see everything together there, with the accruals and the payments and a balance.
开发者1:真的吗?!那好吧,或许这样可行。[拿出其他图表和草图]
Developer 1: Really?! Well in that case, maybe this would work. [Takes other diagram and sketches]
图 11.6. 基于账户的图表,不含交易
Figure 11.6. The account-based diagram, without Transaction
专家:看起来真不错!
Expert: That actually looks pretty good!
开发人员 2:批处理脚本很容易修改,以使用这些新对象。
Developer 2: The batch script will be easy to change to use these new objects.
开发人员 1:新的利息计算器需要几天时间才能正常运行。有很多测试需要修改。但之后测试结果会更清晰。
Developer 1: It will take a few days to get the new Interest Calculator working. There are quite a few tests to change. But the test will read clearer afterward.
两位开发人员着手根据新模型进行重构。在深入研究代码、完善设计的过程中,他们获得了一些新的见解,从而进一步改进了模型。
The two developers went off and started refactoring based on the new model. As they got their hands on the code, tightening up the design, they had insights that refined the model.
分录被细分为付款分录和应计分录,因为仔细检查后发现,在实际应用中,付款分录和应计分录的责任略有不同,而且它们都是重要的领域概念。另一方面,分录本身在概念上或行为上并没有区别,无论其产生是费用还是利息。它们只是简单地出现在相应的账户中。
Entries were subclassed into Payment and Accrual because closer inspection revealed slightly different responsibilities in the application for these, and because they were both important domain concepts. On the other hand, there was no conceptual or behavioral distinction between Entries based on whether they resulted from fees or interest. They simply appeared in the appropriate Account.
然而,不幸的是,开发人员发现他们不得不放弃这最后一层抽象来实现。数据存储在关系表中,项目标准是使这些表无需运行程序即可解释。这意味着费用条目和利息条目必须保存在不同的表中。开发人员实现这一点的唯一方法是使用他们特定的对象关系映射。框架的目的是创建具体的子类(例如费用支付、利息支付等等)。如果采用不同的基础设施,他们或许可以避免这种笨拙的扩展。
Yet, unfortunately, the developers found they had to give up this last abstraction for the implementation. Data was stored in relational tables, and the project standard was to make those tables interpretable without running the program. This meant keeping fee entries and interest entries in separate tables. The only way for developers to do this, using their particular object-relational mapping framework, was to make concrete subclasses (Fee Payments, Interest Payments, and so on). With different infrastructure, they might have avoided this clumsy expansion.
我在这部虚构的故事中加入了这个转折,是为了展现我们经常遇到的现实困境。我们必须做出权衡取舍,然后继续前进,不能让这些妥协动摇我们的模型驱动设计理念。
I threw this twist into this largely fictitious story to represent the rub of reality that we encounter all the time. We have to make calculated compromises and then move on without letting it throw us off our MODEL-DRIVEN DESIGN.
图 11.7. 实现后的类图
Figure 11.7. The class diagram after the implementation
新设计更容易分析和测试,因为最复杂的功能都位于无副作用的函数中。其余命令代码简单(因为它调用了各种函数),并且以断言为特征。
The new design was much easier to analyze and test because the most complex functionality is in SIDE-EFFECT-FREE FUNCTIONS. The remaining command has simple code (because it calls various FUNCTIONS) and is characterized by ASSERTIONS.
有时,我们程序中的某些部分可能根本没有意识到它们可以从领域模型中受益。它们最初可能非常简单,并按照机械的方式逐步演化。它们看起来像是复杂的应用程序代码,而不是领域逻辑。分析模式尤其有助于我们发现这些盲点。
Sometimes there are parts of our programs that we don’t even suspect have the potential to benefit from a domain model. They may have started very simply and evolved mechanistically. They seem like complicated application code, rather than domain logic. Analysis patterns can be particularly helpful in showing us these blind spots.
在以下示例中,开发人员对夜间批处理的黑盒有了新的认识,而此前人们并未将其视为面向领域的。
In the following example, a developer has a new insight into the black box of the nightly batch, which had not been considered domain oriented.
几周后,改进后的基于账户的模型开始逐渐稳定下来。正如常有的情况一样,新设计的清晰性反而凸显了其他问题。负责调整夜间批处理以使其与新设计交互的开发人员(开发人员 2)开始发现批处理的行为与分析模式中的一些概念之间存在联系。以下是他认为最相关的一些概念的总结。
After a few weeks, the improved Account-based model had started to settle in. As often happens, the clarity of the new design made other problems more visible. The developer (Developer 2) who was adapting the nightly batch to interact with the new design began to see connections between the behavior of the batch and some of the concepts in Analysis Patterns. Here is a summary of some of the concepts he found most relevant.
发帖规则
Posting Rules
会计系统通常会提供同一基本财务信息的多个视图。例如,一个账户可能记录收入,而另一个账户可能记录该收入的预估税款。如果系统需要自动更新预估税款账户,那么这两个账户的实现就会紧密交织在一起。有些系统中,大部分账户条目都源于此类规则;在这样的系统中,依赖关系逻辑会变得非常混乱。即使在较为简单的系统中,这种交叉记账也可能很棘手。要理清这些错综复杂的依赖关系,第一步就是通过引入新对象来明确这些规则。
Accounting systems often provide multiple views of the same basic financial information. One account might track income while another might track an estimated tax on that income. If the system is expected to automatically update the estimated tax account, the implementation of those two accounts becomes very intertwined. There are systems in which the majority of account entries result from such rules; in such a system, the dependency logic gets to be a mess. Even in more modest systems, such cross-posting can be tricky. The first step toward taming the tangle of dependencies is to make these rules explicit by introducing a new object.
图 11.8. 基本过账规则的类图
Figure 11.8. The class diagram of the basic posting rule
当“输入”账户中出现新的条目时,记账规则会被触发。然后,它会根据自身的计算方法生成一个新的条目,并将其插入到“输出”账户中。例如,在工资系统中,工资账户中的条目可能会触发一条记账规则,该规则会计算 30% 的预估所得税,并将其作为条目插入到预扣税账户中。
A posting rule is triggered by a new Entry in its “input” account. It then derives a new Entry (based on its own calculation Method) and inserts the new Entry into its “output” Account. In a payroll system, an Entry in a salary Account might trigger a Posting Rule that would calculate a 30 percent estimated income tax and insert it as an Entry in the tax with-holding Account.
过账规则建立了账户之间的概念依赖关系,但如果仅止于此,则可能难以追踪。依赖关系设计中最棘手的部分之一是更新的时机和控制。福勒讨论了三种方案。
The Posting Rule has established the conceptual dependency between Accounts, but if the pattern stopped there, it could be difficult to follow. One of the trickiest parts of dependency designs is the timing and control of updates. Fowler discusses three options.
1. “立即触发”是最显而易见的,但通常也是最不实用的。每当向账户中插入一条记录时,它都会立即触发过账规则,所有更新都会立即生效。
1. “Eager firing” is the most obvious, but typically the least practical. Whenever an Entry is inserted into an Account, it immediately triggers the Posting Rules and all updates are made immediately.
2. “基于账户的触发”允许延迟处理。在某个时间点,系统会向某个账户发送消息,触发该账户的过账规则,以处理自上次触发以来插入的所有条目。
2. “Account-based firing” allows processing to be deferred. At some point, a message is sent to an Account and it triggers its Posting Rules to process all Entries inserted since its last firing.
3.最后,“基于过账规则的触发”由外部代理发起,该代理指示过账规则触发。过账规则负责查找自上次触发以来对其输入账户所做的所有条目。
3. Finally, “Posting-Rule-based firing” is initiated by an external agent, which tells the Posting Rule to fire. The Posting Rule is responsible for looking up all Entries made to its input Accounts since the last time it fired.
尽管系统中可以混合使用不同的触发模式,但每组规则都需要有一个明确定义的启动点,并负责识别输入的账户条目。将这三种触发模式添加到通用语言中,对于模式的成功而言,其重要性不亚于模型对象定义本身。它消除了歧义,并将决策直接引导至一组清晰明确的选择。这些模式揭示了一个容易被忽视的挑战,并提供了支持清晰讨论的词汇。
Although firing modes can be mixed in a system, each particular set of rules needs to have one clearly defined point of initiation and responsibility for identifying input Account Entries. The addition of the three firing modes to the UBIQUITOUS LANGUAGE is as important to the success of the pattern as the model object definitions themselves. It eliminates ambiguity and guides decision making directly to a clearly defined set of choices. These modes identify an easily overlooked challenge and provide vocabulary to support clear discussion.
开发人员 2需要一个可以倾诉的对象来讨论他的新想法。他与他的同事(开发人员 1 )见面,这位开发人员主要负责应计项目的建模。
Developer 2 needed a sounding board to discuss his new ideas. He met up his colleague (Developer 1), the developer who had been primarily responsible for modeling the accruals.
开发人员 2:不知从何时起,夜间批处理脚本开始成为我们掩盖问题的地方。脚本中隐含着领域逻辑,而且变得越来越复杂。我一直想做一件事……对于批处理,我采用了模型驱动设计,分离出一个领域层,并将脚本本身作为领域层之上的简单层。但我始终无法确定这个领域模型究竟应该是什么样子。它似乎只是一些作为对象并不合理的程序。在阅读《分析模式》中关于过账规则的章节时,我获得了一些灵感。以下是我的想法。[递上草图]
Developer 2: At some point, the nightly batch started being a place where we swept stuff under the rug. There is domain logic implicit in what the script does, and it’s been getting more and more complicated. For a long time I’ve wanted to do a model-driven design for the batch, separate out a domain layer, and make the script itself a simple layer on top of the domain. But I could never figure out what that domain model would be like. It seemed like maybe it was just some procedures that didn’t really make sense as objects. As I’ve been reading the section in Analysis Patterns on Posting Rules, I’ve been getting some ideas. Here’s what I had in mind. [Hands over a sketch]
图 11.9. 尝试在批处理中使用过账规则
Figure 11.9. A shot at using Posting Rules in the batch
开发者 1:什么是“发布服务”?
Developer 1: What is this “Posting Service”?
开发者 2:这是一个外观模式,它暴露了会计应用程序的 API,并将其呈现为一项服务。实际上,我之前为了简化批处理代码而创建了这个功能,它还为我提供了一个能够揭示意图的接口,用于向旧系统发布信息。
Developer 2: That is a FACADE that exposes the accounting application’s API and presents it as a SERVICE. I actually made that a while back to simplify the batch code, and it also gave me an INTENTION-REVEALING INTERFACE for posting to the legacy system.
开发者1:有意思。那么,你们计划对这些发布规则采用哪种触发方式?
Developer 1: Interesting. So, which firing style do you plan to use for these Posting Rules?
开发者 2:我还没做到那一步呢。
Developer 2: I hadn’t really gotten that far.
开发人员 1:对于应计项目,预付功能是有效的,因为批处理实际上会告诉资产插入它们,但对于付款项目则无效,因为付款项目是在一天中输入的。
Developer 1: Eager Firing would work for Accruals, since the batch actually tells the Asset to insert them, but it wouldn’t work for Payments, which get entered during the day.
开发人员 2:我觉得我们无论如何都不应该把计算方法和批处理绑定得那么紧密。如果我们以后决定在其他时间触发利息计算,就会出问题。而且从概念上讲,这样做也不太合适。
Developer 2: I don’t think we would want to couple the calculation method that tightly to the batch anyway. If we ever decided to trigger interest calculations at a different time, it would mess things up. And it just doesn’t seem right, conceptually.
开发者 1:听起来像是基于发布规则的。触发。批处理指令会通知每个过账规则执行,规则会查找相应的新条目,然后执行其操作。这和你画的差不多。
Developer 1: It sounds like Posting-Rule-based firing. The batch tells each Posting Rule to execute, and the rule goes and looks for appropriate new Entries and then does its thing. That’s pretty much the way you’ve drawn it.
开发人员 2:这样我们就避免了对批处理设计的过多依赖,而且批处理本身也能保持控制权。听起来没错。
Developer 2: So then we avoid creating a lot of dependencies on the batch design, and the batch keeps control. That sounds right.
开发者 1:我对这些对象与账户和条目之间的交互仍然有些模糊不清。
Developer 1: I’m still a little vague on the interaction of these objects with the Accounts and Entries.
开发人员 2:你我都这么认为。书中的示例在账户和过账规则之间建立了直接联系。这听起来合乎逻辑,但我觉得不太适合我们。我们每次都需要根据数据实例化这些对象,因此必须先确定适用的规则才能进行关联。而资产对象则知道每个账户的内容,因此也知道应该应用哪条规则。那么,剩下的部分呢?
Developer 2: You and me both. The examples in the book create a direct link between the Accounts and the Posting Rules. That is kind of logical, but I don’t think it will work very well for us. We have to instantiate these objects from data each time, so we would have to figure out which rule applies in order to associate it. Meanwhile, the Asset object is the one that knows the content of each Account, and therefore which rule to apply. Anyway, what about the rest of this?
开发人员 1:我不想吹毛求疵,但我认为我们对“方法”的使用方式不对。我认为“方法”的概念是计算要记账的金额——比如,预扣 20% 的所得税。但在我们的例子中,这很简单:总是记账全额。我认为记账规则本身应该知道要记入哪个账户,这对应于我们的“账簿名称”。
Developer 1: I hate to nitpick, but I don’t think that we’re using “Method” right. I think the concept is that the Method computes the amount to be posted—like, say, a 20 percent tax with-holding on income. But in our case, that’s simple: it’s always the full amount being posted. I think the Posting Rule itself is supposed to know which Account to post to, which corresponds to our “ledger name.”
开发人员 2:哦。所以如果过账规则负责知道正确的账簿名称,我们可能根本不需要方法。
Developer 2: Oh. So if the Posting Rule is responsible for knowing the correct ledger name, we probably don’t need Method at all.
实际上,选择合适的账簿名称这件事变得越来越复杂。它已经涉及到收入类型(手续费或利息)和“资产类别”(企业对每项资产的分类)的结合。我希望这个新模型能够在这方面有所帮助。
Actually, this whole business of choosing the right ledger name is getting more and more complicated. It is already a combination of the type of income (fee or interest) with the “asset class” (a category the business applies to each Asset). That is one place I’m hoping this new model will help.
开发人员 1:好的,我们重点关注这里。过账规则负责根据账户属性选择账簿。目前,我们可以用一种简单的方式来处理资产类别以及利息和费用之间的区别。将来,您将拥有一个可以增强的对象模型,以便处理更复杂的情况。
Developer 1: OK, let’s focus there. The Posting Rule is responsible for choosing the ledger based on attributes of the Account. For now, we can make it a straightforward way to handle asset class and the distinction between interest and fees. In the future, you’ll have an OBJECT MODEL you can enhance to handle more complex cases.
开发者2:我需要再仔细考虑一下。让我仔细琢磨琢磨,重新研究一下模式,然后再尝试一下。明天下午我能再跟你讨论一下吗?
Developer 2: I need to think about this some more. Let me mull it over, and reread the patterns, and then I’ll take another stab at it. Could I talk with you about this again tomorrow afternoon?
接下来的几天里,两位开发人员制定了一个模型并重构了代码,使得批处理程序只需遍历资产,向每个资产发送几条不言自明的消息,然后提交数据库事务。复杂性被转移到了领域层,对象模型使之更加明确和抽象。
Over the next few days, the two developers worked out a model and refactored the code so that the batch simply iterated through the Assets, sending a few self-explanatory messages to each and then committing the database transactions. The complexity was shifted into the domain layer, where an object model made it both more explicit and more abstract.
图 11.10. 带有发布规则的类图
Figure 11.10. The class diagram with Posting Rules
图 11.11. 显示规则触发的序列图
Figure 11.11. Sequence diagram showing rule firing
开发人员在细节上与分析模式中提出的模型有很大出入,但他们认为保留了概念的精髓。他们对将资产纳入过账规则的选择中感到有些不安。之所以采用这种方式,是因为资产对象了解每个账户的性质(费用或利息),而且也是脚本的自然访问点。如果将规则对象直接与账户关联,则需要在每次实例化对象时(每次运行批处理脚本时)都与资产对象进行交互。因此,他们让资产对象通过其单例访问查找两个相关的规则,并将相应的账户传递给它们。这似乎使代码更加简洁,因此他们做出了一个务实的决定。
The developers departed considerably from the details of the models presented in Analysis Patterns, yet they felt they had preserved the essence of the concepts. They were a little uncomfortable about involving the Asset in the selection of the Posting Rule. They went that way because the Asset had the knowledge of the nature of each Account (fee or interest) and was also the natural access point for the script. To have associated the rule object directly with the Account would have required a collaboration with the Asset object on each instantiation of the objects (each time the batch was run). Instead, they let the Asset object look up the two relevant rules through their SINGLETON access and pass them the appropriate Account. It seemed to make the code much more direct and so they made a pragmatic decision.
他们都认为,从概念上讲,最好只将过账规则与账户关联起来,而让资产专注于生成应计项目。他们希望后续的重构和更深入的思考能让他们重新审视这一点,并找到一种既能清晰划分规则又不影响代码简洁性的方法。
They both felt that conceptually it would have been better to associate Posting Rules only with Accounts, while keeping the Asset focused on its job of generating Accruals. They hoped that subsequent refactorings and deeper insight would bring them back to this and show them a way to make this clean division without losing the obviousness of the code.
当你幸运地找到一个分析模式时,它几乎不可能完全满足你的特定需求。然而,它能为你的调查提供宝贵的线索,并提供清晰抽象的词汇。它还应该为你提供实施方面的指导,从而避免日后出现问题。
When you are lucky enough to have an analysis pattern, it hardly ever is the answer to your particular needs. Yet it offers valuable leads in your investigation, and it provides cleanly abstracted vocabulary. It should also give you guidance about implementation consequences that will save you pain down the road.
所有这些都推动着知识的不断积累和重构,从而获得更深刻的洞察,并促进开发。最终成果通常与分析模式中记录的形式相似,但会根据具体情况进行调整。有时,最终成果甚至与分析模式本身没有明显的关联,但却受到了模式中洞察的启发。
All this feeds into the dynamo of knowledge crunching and refactoring toward deeper insight and stimulates development. The result often resembles the form documented in the analysis pattern, but adapted to circumstances. Sometimes the result doesn’t even obviously relate to the analysis pattern itself, yet was stimulated by the insights from the pattern.
有一种改变你应该避免。当你使用来自知名分析模式的术语时,无论其表面形式如何变化,都务必保持其所指代的基本概念不变。原因有二。首先,该模式可能蕴含着有助于你避免问题的理解。其次,更重要的是,当你的通用语言包含广为人知或至少易于理解的术语时,你的通用语言能力会得到提升。解释一下。如果你的模型定义随着模型的自然演进而改变,那么也请务必相应地更改名称。
There is one kind of change you should avoid. When you use a term from a well-known analysis pattern, take care to keep the basic concept it designates intact, however much the superficial form might change. There are two reasons for this. First, the pattern may embed understanding that will help you avoid problems. Second, and more important, your UBIQUITOUS LANGUAGE is enhanced when it includes terms that are widely understood or at least well explained. If your model definitions change through the natural evolution of the model, take the trouble to change the names too.
关于对象模型的论述汗牛充栋,有些专门针对特定行业中的特定应用,有些则相当通用。大多数模型都提供了初步的思路,但只有少数模型能够捕捉到选择背后的逻辑以及由此产生的后果,而这些恰恰是分析模式中最有价值的部分。如果能有更多这类精细化的分析模式,将会非常有价值,可以帮助我们避免重复造轮子。我估计很难看到一个全面的目录,但或许会出现一些行业特定的目录。而某些跨多个应用领域的模式则可以被广泛共享。
Quite a lot of object models have been written about, some specialized for one kind of application in one industry and some quite general. Most of them provide the seed of an idea, but only a few have captured the reasoning behind the choices and the consequences that follow, which are the most useful parts of an analysis pattern. More of these refined analysis patterns would be valuable, to help save us from reinventing the wheel again and again. I’d be surprised ever to see a comprehensive catalog, but industry-specific catalogs might arise. And patterns for some domains that cross many applications could be widely shared.
这种对组织化知识的重新应用与通过框架或组件重用代码的尝试截然不同,尽管两者都可能提供一些不易察觉的灵感。模型,即使是通用框架,也是一个完整的整体,而分析则是一系列模型片段的集合。分析模式专注于最关键、最棘手的决策,并阐明各种替代方案和选择。它们能够预见后续可能产生的后果,而这些后果如果由用户自行发现,则会付出高昂的代价。
This kind of reapplication of organized knowledge is completely different from attempts to reuse code through frameworks or components, except that either could provide the seed of an idea that is not obvious. A model, even a generalized framework, is a complete working whole, while an analysis is a kit of model fragments. Analysis patterns focus on the most critical and difficult decisions and illuminate alternatives and choices. They anticipate downstream consequences that are expensive if you have to discover them for yourself.
本书迄今为止探讨的模式旨在解决模型驱动设计中领域模型的问题。然而,实际上,迄今为止发表的大多数模式更侧重于技术层面。设计模式和领域模式之间有什么区别?首先,经典著作《设计模式》的作者是这样解释的:
The patterns explored in this book so far are intended specifically for solving problems in a domain model in the context of a MODEL-DRIVEN DESIGN. Actually, though, most of the patterns published to date are more technical in focus. What is the difference between a design pattern and a domain pattern? For starters, the authors of the seminal book, Design Patterns, had this to say:
视角会影响人们对模式的理解。一个人眼中的模式,在另一个人看来可能只是最基本的构建模块。本书着重探讨的是特定抽象层次上的模式。设计模式并非指链表和哈希表这类可以编码成类并直接复用的设计,也不是针对整个应用程序或子系统的复杂、领域特定的设计。本书中的设计模式描述的是可交互的对象和类,它们经过定制,旨在解决特定上下文中的通用设计问题。[ Gamma et al. 1995 , p. 3]
Point of view affects one’s interpretation of what is and isn’t a pattern. One person’s pattern can be another person’s primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. Design patterns are not about designs such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are they complex, domain-specific designs for an entire application or subsystem. The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context. [Gamma et al. 1995, p. 3]
设计模式中的部分模式(并非全部)可以用作领域模式。这样做需要转变关注点。设计模式提供了一个设计元素目录,这些元素解决了各种场景下常见的难题。这些模式的动机和模式本身都是以纯技术术语来阐述的。但是,其中一部分元素可以应用于……更广泛的领域建模和设计背景,因为它们对应于许多领域中出现的一般概念。
Some, not all, of the patterns in Design Patterns can be used as domain patterns. Doing so requires a shift in emphasis. Design Patterns presents a catalog of design elements that have solved problems commonly encountered in a variety of contexts. The motivations of these patterns and the patterns themselves are presented in purely technical terms. But a subset of these elements can be applied in the broader context of domain modeling and design, because they correspond to general concepts that emerge in many domains.
除了设计模式之外,多年来还提出了许多其他技术设计模式。其中一些模式对应于特定领域中涌现的深层概念。借鉴这些研究成果将大有裨益。为了在领域驱动设计中运用这些模式,我们需要同时从两个层面来审视它们。一方面,它们是代码中的技术设计模式;另一方面,它们是模型中的概念模式。
In addition to those in Design Patterns, there have been many other technical design patterns presented over the years. Some of them correspond to deep concepts that emerge in domains. It would be nice to draw on this work. To make use of such patterns in domain-driven design, we have to look at the patterns on two levels simultaneously. On one level, they are technical design patterns in the code. On the other level, they are conceptual patterns in the model.
设计模式中的一些具体模式示例将展示如何将设计模式应用于领域模型,并阐明技术设计模式和领域模式之间的区别。“组合模式”和“策略模式”将展示如何通过不同的思考方式,将一些经典设计模式应用于领域问题。
A sample of specific patterns from Design Patterns will show how a pattern conceived as a design pattern can be applied in the domain model, and it will clarify the distinction between a technical design pattern and a domain pattern. COMPOSITE and STRATEGY demonstrate how some of the classic design patterns can be applied to domain problems by thinking about them in a different way. . . .
定义一系列算法,封装每个算法,并使它们可以互换。该策略允许算法独立于使用它的客户端而变化。[ Gamma et al. 1995 ]
Define a family of algorithms, encapsulate each one, and make them interchangeable. STRATEGY lets the algorithm vary independently from clients that use it. [Gamma et al. 1995]
领域模型包含一些并非出于技术动机,而是在问题领域中具有实际意义的流程。当需要提供多种备选流程时,选择合适流程的复杂性会与多个流程本身的复杂性叠加,导致情况失控。
Domain models contain processes that are not technically motivated but actually meaningful in the problem domain. When alternative processes must be provided, the complexity of choosing the appropriate process combines with the complexity of the multiple processes themselves, and things get out of hand.
当我们对流程进行建模时,常常会发现实现该流程的方法不止一种。当我们开始描述这些选项时,我们对流程的定义就会变得笨拙而复杂。我们实际选择的行为方案会因为与其他行为混杂在一起而变得模糊不清。
When we model processes, we often realize that there is more than one legitimate way of doing them. As we start to describe these options, our definition of the process becomes clumsy and complicated. The actual behavioral alternatives we are choosing between are obscured as they are mixed in with the rest of the behavior.
我们希望将这种变化与流程的核心概念区分开来。这样,我们就能更清晰地看到流程的核心及其各种选项。软件设计领域已经广泛应用的STRATEGY模式正是为了解决这个问题,尽管它的重点在于技术层面。在这里,STRATEGY模式被用作模型中的一个概念,并体现在该模型的代码实现中。同样,我们也需要将流程中高度可变的部分与较为稳定的部分解耦。
We would like to separate this variation from the main concept of the process. Then we would be able to see both the main process and the options more clearly. The STRATEGY pattern, already well established in the software design community, addresses this very issue, though the focus is technical. Here it is being applied as a concept in a model and reflected in the code implementation of that model. There is the same need to decouple the highly variable part of the process from the more stable part.
将流程中可变的部分分解成模型中一个单独的“策略”对象。将规则及其所控制的行为分离出来。按照策略设计模式实现规则或可替代流程。策略对象的多个版本代表了流程的不同实现方式。
Factor the varying part of a process into a separate “strategy” object in the model. Factor apart a rule and the behavior it governs. Implement the rule or substitutable process following the STRATEGY design pattern. Multiple versions of the strategy object represent different ways the process can be done.
传统上,STRATEGY作为一种设计模式,其关注点在于能够替代不同的算法;而作为一种领域模式,STRATEGY 则更注重表达概念,通常是指一个过程或策略规则。
Whereas the conventional view of STRATEGY as a design pattern focuses on the ability to substitute different algorithms, its use as a domain pattern focuses on its ability to express a concept, usually a process or a policy rule.
路线规范被传递给路线规划服务,该服务的任务是构建满足该规范的详细行程。该服务是一个优化引擎,可以进行调整以找到最快路线或最便宜路线。
A Route Specification is being passed to a Routing Service, whose job is to construct a detailed Itinerary that satisfies the SPECIFICATION. This SERVICE is an optimization engine that can be tuned to find either the fastest route or the cheapest one.
图 12.1.具有选项的SERVICE接口需要条件逻辑。
Figure 12.1. A SERVICE interface with options will need conditional logic.
这种设置看起来没问题,但仔细查看路由代码就会发现,每个计算步骤都用到了条件语句,导致在最快路线和最便宜路线之间做出选择时显得非常随意。如果添加新的标准来对路线进行更精细的选择,问题会更加严重。
This setup looks OK, but a detailed look at the routing code would reveal conditionals in every computation, making the decision between fastest or cheapest appear all over the place. More trouble will come when new criteria are added to make more subtle choices between routes.
一种方法是将这些调优参数拆分成不同的策略。然后,它们可以被显式地表示出来,并作为参数传递给路由服务。
One approach is to separate those tuning parameters into STRATEGIES. Then they can be represented explicitly, passed into the Routing Service as a parameter.
路由服务现在以相同的、无条件的方式处理所有请求,寻找幅度较小的链路序列,幅度由链路幅度策略计算得出。
The Routing Service now handles all requests in the same, unconditional way, looking for a sequence of Legs with a low magnitude, as computed by the Leg Magnitude Policy.
这种设计具备设计模式中策略模式(STRATEGY pattern)的优势。在应用的通用性和灵活性方面,现在可以通过安装合适的航段量策略(Leg Magnitude Policy)来控制和扩展路由服务的行为。图 12.2中所示的策略(最快或最便宜)只是最显而易见的几种。兼顾速度和成本的组合方案可能更常见。此外,可能还存在其他因素,例如公司倾向于使用自有运输工具而非外包给其他航运公司来运输货物。这些修改无需使用策略模式也能实现,但逻辑会深入路由服务的内部,导致接口臃肿。而解耦设计使其清晰易懂,便于测试。
This design has the advantages that motivate the STRATEGY pattern in Design Patterns. On the level of application versatility and flexibility, the behavior of the Routing Service can now be controlled and extended by installing an appropriate Leg Magnitude Policy. The STRATEGIES illustrated in Figure 12.2 (fastest or cheapest) are only the most obvious ones. Combinations that balance speed and cost are likely. There may be other factors altogether, such as a bias toward booking cargo on the company’s own transports rather than subcontracting to carry them on the transports of other shipping companies. These modifications could have been made without resorting to STRATEGIES, but the logic would have wound through the internals of the Routing Service and bloated its interface. The decoupling does make it clear and easily testable.
图 12.2. 由作为论据传递的策略(政策)选择所决定的选项
Figure 12.2. Options determined by choice of STRATEGY (POLICY) passed as argument
在该领域中,一条至关重要的规则——即在构建行程时选择航段的依据——如今已明确且清晰地呈现出来。它表明,航段的某个特定属性(可能由其他属性衍生而来)被简化为一个数值,作为路线规划的基础。这使得我们可以用领域语言简单地描述路线规划服务的行为:路线规划服务选择具有最小值的行程。根据所选策略,各腿的总强度。
A fundamentally important rule in the domain, the basis of choosing one Leg over another when building an Itinerary, is now explicit and distinct. It conveys the knowledge that a specific attribute (potentially derived) of an individual leg, boiled down to a single number, is the basis for routing. This makes possible a simple statement in the language of the domain that defines the Routing Service’s behavior: The Routing Service chooses an Itinerary with a minimum total magnitude of the Legs based on the chosen STRATEGY.
注意:此处的讨论暗示路由服务在搜索行程时实际上会评估航段。这种方法在概念上很简单,也可以实现一个合理的原型,但效率可能低得无法接受。我们将在第 14 章“维护模型完整性”中再次讨论此应用,届时将使用相同的接口,但路由服务的实现方式完全不同。
Note: This discussion implies that the Routing Service is actually evaluating Legs as it searches for an Itinerary. This approach is conceptually straightforward, and it could make a reasonable prototype implementation, but it is probably unacceptably inefficient. This application will be taken up again in Chapter 14, “Maintaining Model Integrity,” where the same interface will be used with a completely different implementation of the Routing Service.
当我们在领域层使用技术设计模式时,我们需要添加额外的动机,赋予其另一层含义。当策略与实际的业务战略或政策相对应时,该模式就不仅仅是一种有用的实现技术(尽管就其本身而言,这种技术也很有价值)。
When we use the technical design pattern in the domain layer, we have to add an additional motivation, another layer of meaning. When the STRATEGY corresponds to an actual business strategy or policy, the pattern becomes more than just a useful implementation technique (though that too is valuable as far as it goes).
设计模式的后果完全适用。例如,在《设计模式》一书中,Gamma 等人指出,客户端必须了解不同的策略(STRATEGIES) ,这也是建模方面需要考虑的问题。纯粹从实现角度来看,策略可能会增加应用程序中的对象数量。如果这是一个问题,可以通过将策略实现为上下文可以共享的无状态对象来减少开销。 《设计模式》中对实现方法的详尽讨论在这里同样适用。这是因为我们仍然在使用策略。我们的动机虽然有所不同,这会影响一些选择,但设计模式中蕴含的经验仍然可供我们借鉴。
The consequences of the design pattern fully apply. For example, in Design Patterns, Gamma et al. point out that clients must be aware of different STRATEGIES, which is also a modeling concern. A concern purely of implementation is that STRATEGIES can increase the number of objects in the application. If that is a problem, the overhead can be reduced by implementing STRATEGIES as stateless objects that contexts can share. The extensive discussion of implementation approaches in Design Patterns all applies here. This is because we are still using a STRATEGY. Our motivations are partially different, which will affect some choices, but the experience embedded in the design pattern is at our disposal.
将对象组合成树状结构以表示部分-整体层次结构。COMPOSI允许客户端以统一的方式处理单个对象和对象组合。[ Gamma 等人,1995 ]
Compose objects into tree structures to represent part-whole hierarchies. COMPOSITE lets clients treat individual objects and compositions of objects uniformly. [Gamma et al. 1995]
在对复杂领域进行建模时,我们经常会遇到这样的情况:一个重要的对象由多个部分组成,而这些部分本身又由更多部分组成,如此往复——有时甚至可以嵌套到任意深度。在某些领域,每一层在概念上都是截然不同的;但在其他情况下,各个部分在某种意义上与整体是同一类事物,只是规模更小而已。
We often encounter, while modeling complex domains, an important object that is composed of parts, which are themselves made up of parts, which are made up of parts—occasionally even nesting to arbitrary depth. In some domains, each of these levels is conceptually distinct, but in other cases, there is a sense in which the parts are the same kind of thing as the whole, only smaller.
当模型中未体现嵌套容器之间的关联性时,层级结构的每一层都需要重复执行相同的行为,且嵌套方式较为僵化(例如,容器通常不能包含同层的其他容器,层级数量也是固定的)。即使客户端可能并不关心层级结构中存在的概念差异,他们也必须通过不同的接口来处理层级结构的不同层级。递归遍历层级结构以生成聚合信息的过程也变得非常复杂。
When the relatedness of nested containers is not reflected in the model, common behavior has to be duplicated at each level of the hierarchy, and nesting is rigid (for example, containers can’t usually contain other containers at their own level, and the number of levels is fixed). Clients must deal with different levels of the hierarchy through different interfaces, even though there may be no conceptual difference they care about. Recursion through the hierarchy to produce aggregated information is very complicated.
在领域应用任何设计模式时,首先要考虑的是该模式的理念是否真正契合领域概念。递归地遍历一些关联对象或许很方便,但是否存在真正的整体-部分层次结构?你是否找到了一种抽象,使得所有部分在其下都真正相互关联?概念类型相同吗?如果相同,COMPOSITE将使模型的这些方面更加清晰,同时允许您利用设计模式中精心考虑的设计和实现方面的考量。
When applying any design pattern in the domain, the first concern should be whether the pattern idea really is a good fit for the domain concept. It might be convenient to move recursively through some associated objects, but is there a true whole-part hierarchy? Have you found an abstraction under which all the parts truly are the same conceptual type? If you have, COMPOSITE will make those aspects of the model clearer, while allowing you to tap into the carefully thought-out design and implementation considerations of the design pattern.
所以:
Therefore:
定义一个抽象类型,该类型涵盖COMPOSITE的所有成员。容器上实现了返回信息的方法,用于返回关于其内容的聚合信息。“叶”节点根据自身的值实现这些方法。客户端直接处理抽象类型,无需区分叶节点和容器。
Define an abstract type that encompasses all members of the COMPOSITE. Methods that return information are implemented on containers to return aggregated information about their contents. “Leaf” nodes implement those methods based on their own values. Clients deal with the abstract type and have no need to distinguish leaves from containers.
从结构层面来看,这是一种相对显而易见的模式,但设计师往往不会深入探究其在操作层面的内涵。“复合”模式在每个结构层面都表现出相同的特性,因此可以针对大小部件提出有意义的问题,这些问题能够清晰地反映出部件的构成。这种严谨的对称性正是该模式强大之处的关键所在。
This is a relatively obvious pattern on the structural level, but designers often do not push themselves to flesh out the operational level of the pattern. The COMPOSITE offers the same behavior at every structural level, and meaningful questions can be asked of small or large parts that transparently reflect their makeup. That rigorous symmetry is the key to the power of the pattern.
完整的货物运输路线非常复杂。首先,集装箱必须用卡车运到铁路货运站,然后运到港口,再用船运到另一个港口,可能还要转运到其他船只上,最后在目的地通过陆路运输。
A complete cargo shipment route is complicated. First the container must be trucked to a railhead, then carried to a port, then transported on a ship to another port, possibly transferred to other ships, and finally transported by ground on the other end.
图 12.3. 由“支路”组成的“路线”示意图
Figure 12.3. A schematic of a “route” made up of “legs”
应用程序开发团队创建了一个对象模型,用于表达组成路线的任意长度的线路。
An application development team has created an object model to express these arbitrarily long strings of legs that assemble into a route.
图 12.4. 由路段组成的路线的类图
Figure 12.4. A class diagram of a Route made up of Legs
利用这种模型,开发人员能够根据预订请求创建路线对象。他们能够将航段处理成运营计划,以便逐步处理货物。然后,他们发现了一些事情。
Using this model, the developers are able to create Route objects based on booking requests. They are able to process the Legs into the operational plan for the step-by-step handling of the cargo. Then they discover something.
开发人员一直把路线视为任意的、没有区别的一系列支路。
The developers had always thought of a route as an arbitrary, un-differentiated string of legs.
图 12.5. 开发人员对路线的构想
Figure 12.5. The developers’ conception of a route
事实证明,领域专家将该路线视为五个逻辑段的序列。
It turns out the domain experts see the route as a sequence of five logical segments.
图 12.6. 商业专家对路线的构想
Figure 12.6. The business experts’ conception of a route
此外,这些子路线可能由不同的人在不同的时间规划,因此必须将它们视为不同的路线。仔细观察,“门腿”与……截然不同。其他运输环节则涉及当地雇佣的卡车,甚至客户自行运输,这与精心安排的铁路和船舶运输形成鲜明对比。
Among other things, these subroutes may be planned at different times by different people, so they have to be viewed as distinct. And on closer inspection, the “door legs” are quite different from the other legs, involving locally hired trucks or even customer haulage, in contrast to the elaborately scheduled rail and ship transports.
反映所有这些区别的对象模型开始变得复杂起来。
An object model reflecting all these distinctions starts to get complicated.
图 12.7. 路线的详细类图
Figure 12.7. The elaborated class diagram of Route
从结构上看,这个模型并不算太糟糕,但它破坏了操作计划处理的统一性,导致代码甚至行为描述都变得更加复杂。其他问题也开始显现。任何路径遍历都涉及多种不同类型对象的集合。
Structurally the model isn’t so bad, but the uniformity of processing the operational plan is lost, so the code, or even a description of behavior, becomes much more complicated. Other complications begin to surface, too. Any traversal of a route involves multiple collections of different types of objects.
引入复合型(COMPOSITE)的概念。对于某些客户而言,如果能将此结构中的不同层级统一视为由下级路线组成的路线,那就更好了。从概念上讲,这种观点是合理的。路线的每一层级都代表着一个容器从一点到另一点的移动,直至具体的支路。(参见图 12.8。)
Enter COMPOSITE. It would be nice, for certain clients, to treat the different levels in this construct uniformly, as routes made up of routes. Conceptually this view is sound. Every level of Route is a movement of a container from one point to another, all the way down to an individual leg. (See Figure 12.8.)
图 12.8. 使用COMPOSITE 的类图
Figure 12.8. A class diagram using COMPOSITE
现在,静态类图不像之前的类图那样能清晰地展示门腿和其他部件是如何组装在一起的。但这个模型不仅仅是一个静态类图。我们将通过其他图表(参见图 12.9 )以及(现在简洁得多的)代码来传达装配信息。该模型捕捉了所有这些不同类型的“路径”之间深层次的关联性。生成操作计划以及其他路径遍历操作都变得简单易行。
Now, the static class diagram does not tell us as much about how door legs and other segments fit together as the previous one did. But the model is more than a static class diagram. We’ll convey assembly information through other diagrams (see Figure 12.9) and through the (now much simpler) code. This model captures the deep relatedness of all these different kinds of “Route.” Generating the operational plan is simple again, as are other route-traversing operations.
图 12.9. 代表完整路线的实例
Figure 12.9. Instances representing a complete Route
通过由其他路径首尾相连拼接而成的路径,可以实现从一个地点到另一个地点的路径配置,从而拥有各种不同细节的路径实现。你可以截断一条路径的末端并拼接上一个新的末端,可以任意嵌套路径的细节,还可以利用各种可能有用的选项。
With a route made of other routes, pieced together end to end to get from one place to another, you can have route implementations of varying detail. You can chop off the end of a route and splice on a new ending, you can have arbitrary nesting of detail, and you can exploit all sorts of possibly useful options.
当然,我们目前还不需要这些选项。在我们需要这些路径段和独立门框之前,没有COMPOSITE 模式我们也做得很好。设计模式应该只在必要时才应用。
Of course, we don’t yet need such options. And before we needed those route segments and distinct door legs, we were doing just fine without COMPOSITE. A design pattern should be applied only when it is needed.
因为我在前面(第五章)提到了享元模式,你可能认为它是应用于领域模型的模式示例。实际上,享元模式恰恰是一个很好的例子,它是一种与领域模型无关的设计模式。
Because I referred to the FLYWEIGHT pattern earlier (in Chapter 5), you might have assumed that it is an example of a pattern to be applied to domain models. In fact, FLYWEIGHT is a good example of a design pattern that has no correspondence to the domain model.
当一组有限的值对象被多次使用时(例如房屋平面图中的电源插座),将它们实现为享元可能更为合理。这是值对象而非实体的一种实现选项。与此形成对比的是组合模式,在组合模式中,概念对象由其他概念对象组成。在这种情况下,该模式同时适用于模型和实现,这是领域模式的一个基本特征。
When a limited set of VALUE OBJECTS is used many times (as in the example of electrical outlets in a house plan), it may make sense to implement them as FLYWEIGHTS. This is an implementation option available for VALUE OBJECTS and not for ENTITIES. Contrast this with COMPOSITE, in which conceptual objects are composed of other conceptual objects. In that case, the pattern applies to both model and implementation, which is an essential trait of a domain pattern.
我不打算列出所有可以作为领域模式的设计模式。虽然我目前想不出用解释器作为领域模式的例子,但我并不认为任何领域概念都不适合这种模式。唯一的要求是,该模式应该能够描述概念领域,而不仅仅是针对技术问题的技术解决方案。
I’m not going to try to compile a list of the design patterns that can be used as domain patterns. Although I can’t think of an example of using an interpreter as a domain pattern, I’m not prepared to say that there is no conception of any domain that would fit. The only requirement is that the pattern should say something about the conceptual domain, not just be a technical solution to a technical problem.
重构以获得更深刻的洞察是一个多方面的过程。不妨停下来片刻,梳理一下要点。你需要关注以下三点。
Refactoring toward deeper insight is a multifaceted process. It will be helpful to stop for a moment to pull together the major points. There are three things you have to focus on.
1.生活在该领域内。
1. Live in the domain.
2.换个角度看问题。
2. Keep looking at things a different way.
3.与领域专家保持不间断的对话。
3. Maintain an unbroken dialog with domain experts.
深入了解领域知识可以为重构过程创造更广泛的背景。
Seeking insight into the domain creates a broader context for the process of refactoring.
经典的重构场景是这样的:一两个开发人员坐在电脑前,发现某些代码可以改进,然后立即进行修改(当然,会编写单元测试来验证修改结果)。这种做法应该经常进行,但这并非重构的全部。
The classic refactoring scenario involves a developer or two sitting at the keyboard, recognizing that some code can be improved, and then changing it on the fly (with unit tests to verify their results, of course). This practice should happen all the time, but it isn’t the whole story.
前五章对重构进行了扩展的阐述,是在传统的微重构方法的基础上进行的。
The previous five chapters present an expanded view of refactoring, superimposed on the conventional micro-refactoring approach.
重构以求获得更深刻的理解可以从很多方面入手。它可能源于代码中存在的问题——例如某些复杂性或不合理之处。与其对代码进行标准的转换,不如……开发人员感觉问题的根源在于领域模型。或许缺少某个概念,或许某些关系有误。
Refactoring toward deeper insight can begin in many ways. It may be a response to a problem in the code—some complexity or awkwardness. Rather than apply a standard transformation of the code, the developers sense that the root of the problem is in the domain model. Perhaps a concept is missing. Maybe some relationship is wrong.
与传统的重构观点不同,即使代码看起来简洁明了,如果模型语言与领域专家的理解脱节,或者新需求无法自然融入现有代码,也可能出现这种重构的意识。重构可能源于学习,因为对领域有了更深入理解的开发者会发现,有机会构建一个更清晰或更有用的模型。
In a departure from the conventional view of refactoring, this same realization could come when the code looks tidy, if the language of the model seems disconnected from the domain experts, or if new requirements are not fitting in naturally. Refactoring might result from learning, as a developer who has gained deeper understanding sees an opportunity for a more lucid or useful model.
发现问题所在往往是最难也是最不确定的环节。之后,开发人员可以系统地寻找新模型的要素。他们可以与同事和领域专家集思广益。他们还可以借鉴以分析模式或设计模式形式编写的系统化知识。
Seeing the trouble spot is often the hardest and most uncertain part. After that, developers can systematically seek out the elements of a new model. They can brainstorm with colleagues and domain experts. They can draw on systematized knowledge written as analysis patterns or design patterns.
无论不满的根源是什么,下一步都是寻求改进方案,使模型能够清晰自然地传达信息。这可能只需要一些显而易见的微小改动,几个小时就能完成。在这种情况下,这种改动类似于传统的重构。但寻找新的模型则可能需要更多的时间和更多人的参与。
Whatever the source of dissatisfaction, the next step is to seek a refinement that will make the model communicate clearly and naturally. This might require only some modest change that is immediately evident and can be accomplished in a few hours. In that case, the change resembles traditional refactoring. But the search for a new model may well call for more time and the involvement of more people.
变革的发起者会挑选几位擅长思考这类问题、熟悉该领域或拥有强大建模能力的开发人员。如果存在一些细节问题,他们会确保有领域专家参与。这四五个人会聚在会议室或咖啡馆,进行半小时到一个半小时的头脑风暴。他们绘制 UML 图,尝试使用对象来演练各种场景。他们确保领域专家理解模型并认为它有用。当他们找到满意的方案时,就会着手编写代码。或者,他们决定先思考几天,然后再去做其他事情。几天后,小组再次聚首,重复上述步骤。这一次,经过深思熟虑,他们更加自信,并得出了一些结论。然后,他们回到电脑前,开始编写新设计的代码。
The initiators of the change pick a couple of other developers who are good at thinking through that kind of problem, who know that area of the domain, or who have strong modeling skills. If there are subtleties, they make sure a domain expert is involved. This group of four or five people goes to a conference room or a coffee shop and brainstorms for half an hour to an hour and a half. They sketch UML diagrams; they try walking through scenarios using the objects. They make sure the subject matter expert understands the model and finds it useful. When they find something they are happy with, they go back and code it. Or they decide to mull it over for a few days, and they go back and work on something else. A couple of days later, the group reconvenes and goes through the exercise again. This time they are more confident, having slept on their earlier thoughts, and they reach some conclusions. They go back to their computers and code the new design.
There are a few keys to keeping this process productive.
•自主性。可以随时组建一个小团队来探索设计问题。团队可以运作几天后解散。无需长期、复杂的组织结构。
• Self-determination. A small team can be assembled on the fly to explore a design problem. The team can operate for a few days and then disband. There is no need for long-term, elaborate organizational structures.
•范围界定和时间安排。几天内安排两到三次简短的会议,应该就能得出值得尝试的设计方案。拖延只会适得其反。如果遇到瓶颈,可能是因为你一次性承担了太多工作。选择设计中较小的方面,集中精力攻克它。
• Scope and sleep. Two or three short meetings spaced out over a few days should produce a design worth trying. Dragging it out doesn’t help. If you get stuck, you may be taking on too much at once. Pick a smaller aspect of the design and focus on that.
•运用通用语言。让其他团队成员(尤其是领域专家)参与头脑风暴会议,可以创造机会来运用和完善通用语言。最终成果是对该语言的改进,原开发者将把改进后的语言带回并编写成代码。
• Exercising the UBIQUITOUS LANGUAGE. Involving the other team members—particularly the subject matter expert—in the brainstorming session creates an opportunity to exercise and refine the UBIQUITOUS LANGUAGE. The end result of the effort is a refinement of that LANGUAGE which the original developer(s) will take back and formalize in code.
本书前几章介绍了开发人员和领域专家探讨更优模型的几场对话。一场完整的头脑风暴会议充满活力、形式灵活,而且效率极高。
Earlier chapters in this book have presented several dialogs in which developers and domain experts probe for better models. A full-blown brainstorming session is dynamic, unstructured, and incredibly productive.
并非总是需要重新发明轮子。集思广益,寻找缺失的概念和更优的模型,能够有效地吸收来自各种来源的想法,并将它们与本地知识相结合,不断深入探讨,最终找到解决当前问题的方案。
It isn’t always necessary to reinvent the wheel. The process of brainstorming for missing concepts and better models has a great capacity to absorb ideas from any source, combine them with local knowledge, and continue crunching to find answers to the current situation.
你可以从书籍和其他领域知识来源中获取灵感。虽然该领域的专家可能没有创建适合运行软件的模型,但他们很可能已经整理了相关概念并找到了一些有用的抽象概念。以这种方式为知识处理过程提供素材,可以更快地获得更丰富、更快速的结果,而且这些结果对于领域专家来说也可能更容易理解和接受。
You can get ideas from books and other sources of knowledge about the domain itself. Although the people in the field may not have created a model suitable for running software, they may well have organized the concepts and found some useful abstractions. Feeding the knowledge-crunching process this way leads to richer, quicker results that also will probably seem more familiar to domain experts.
有时你可以借鉴他人的经验,将其转化为分析模式。这种输入方式与阅读领域相关书籍有一定效果,但在此情况下,它是专门针对特定领域的。软件开发应该直接基于你在自身领域内的软件开发经验。分析模式可以为你提供精妙的模型概念,并帮助你避免许多错误。但它们并非现成的模板,而是为知识积累过程提供养分。
Sometimes you can draw on the experience of others in the form of analysis patterns. This kind of input has some of the effect of reading about the domain, but in this case it is geared specifically toward software development, and it should be based directly on experience implementing software in your domain. Analysis patterns can give you subtle model concepts and help you avoid lots of mistakes. But they don’t give you a cookbook recipe. They feed the knowledge-crunching process.
在将各个部分组装起来的过程中,模型问题和设计问题必须并行处理。再次强调,这并不总是意味着要从零开始创建一切。当设计模式既符合实现需求又符合模型概念时,通常可以在领域层应用它们。
As the pieces are fit together, model concerns and design concerns must be dealt with in parallel. Again, it doesn’t always mean inventing everything from scratch. Design patterns can often be employed in the domain layer when they fit both an implementation need and the model concept.
同样地,当算术逻辑或谓词逻辑等常用形式体系适用于某个领域的部分内容时,你可以将该部分提取出来,并调整形式体系的规则。这样就能得到非常严谨且易于理解的模型。
Likewise, when a common formalism, such as arithmetic or predicate logic, fits some part of a domain, you can factor that part out and adapt the rules of the formal system. This provides very tight and readily understood models.
软件不仅服务于用户,也服务于开发者。开发者需要将代码与其他系统组件集成。在一个迭代过程中,开发者会不断地修改代码。重构以获得更深入的理解,既能带来灵活的设计,也能从灵活的设计中受益。
Software isn’t just for users. It’s also for developers. Developers have to integrate code with other parts of the system. In an iterative process, developers change the code again and again. Refactoring toward deeper insight both leads to and benefits from a supple design.
灵活的设计能够清晰地传达其意图。这种设计使得代码运行的效果易于预测,因此也易于预测代码更改的后果。灵活的设计有助于减轻用户的认知负担,主要体现在减少依赖关系和副作用方面。它基于对领域的深度模型,仅在对用户至关重要的方面进行细粒度划分。这使得在变更频繁的领域能够保持灵活性,而在其他领域则保持简洁性。
A supple design communicates its intent. The design makes it easy to anticipate the effect of running code—and therefore it easy to anticipate the consequences of changing it. A supple design helps limit mental overload, primarily by reducing dependencies and side effects. It is based on a deep model of the domain that is fine-grained only where most critical to the users. This makes for flexibility where change is most common, and simplicity elsewhere.
如果你等到能够完全证明修改的合理性才去做,那就太晚了。你的项目已经耗费了大量成本,而且由于目标代码更加复杂,与其他代码的集成度也更高,推迟修改会更加困难。
If you wait until you can make a complete justification for a change, you’ve waited too long. Your project is already incurring heavy costs, and the postponed changes will be harder to make because the target code will have been more elaborated and more embedded in other code.
持续重构已被视为一种“最佳实践”,但大多数项目团队对此仍然过于谨慎。他们看到修改代码的风险以及开发人员为此花费的时间成本固然显而易见;但更难察觉的是,保留一个笨拙的设计所带来的风险以及为了绕过这个设计而进行调整的成本。想要重构的开发人员常常被要求解释其决定。虽然这看似合理,但却让原本就困难的事情变得更加复杂,并往往会扼杀重构(或将其转入地下)。软件开发并非一个可预测的过程,因此无法准确计算出变更的收益或不变更的成本。
Continuous refactoring has come to be considered a “best practice,” but most project teams are still too cautious about it. They see the risk of changing code and the cost of developer time to make a change; but what’s harder to see is the risk of keeping an awkward design and the cost of working around that design. Developers who want to refactor are often asked to justify the decision. Although this seems reasonable, it makes an already difficult thing impossibly difficult, and tends to squelch refactoring (or drive it underground). Software development is not such a predictable process that the benefits of a change or the costs of not making a change can be accurately calculated.
为了更深入地理解问题,重构需要成为持续探索领域主题、培训开发人员以及开发人员与领域专家交流思想的一部分。因此,重构应在以下情况下进行:
Refactoring toward deeper insight needs to become part of the ongoing exploration of the subject matter of the domain, the education of the developers, and the meeting of the minds of developers and domain experts. Therefore, refactor when
• 该设计未能表达团队目前对该领域的理解;
• The design does not express the team’s current understanding of the domain;
• 设计中隐含着一些重要概念(而你找到了将它们明确表达出来的方法);或者
• Important concepts are implicit in the design (and you see a way to make them explicit); or
• 你看到了成为设计供应商重要组成部分的机会。
• You see an opportunity to make some important part of the design suppler.
这种激进的态度并不能成为随时进行任何更改的理由。不要在发布前一天进行重构。不要引入那些仅仅炫耀技术技巧却未能触及领域核心的“灵活设计”。不要引入那些你无法说服领域专家使用的“更深层次模型”,无论它看起来多么优雅。不要固执己见,但要努力突破舒适区,朝着有利于重构的方向迈进。
This aggressive attitude does not justify any change at any time. Don’t refactor the day before a release. Don’t introduce “supple designs” that are just demonstrations of technical virtuosity but fail to cut to the core of the domain. Don’t introduce a “deeper model” that you couldn’t convince a domain expert to use, no matter how elegant it seems. Don’t be absolute about things, but push beyond the comfort zone in the direction of favoring refactoring.
在查尔斯·达尔文提出进化论后的一个多世纪里,标准的进化模型认为物种会随着时间的推移而逐渐、相对稳定地发生变化。然而,到了20世纪70年代,这一模型突然被“间断平衡”模型所取代。在这种扩展的进化论观点中,漫长的渐进变化或稳定期会被相对较短的快速变化期所打断。之后,事物会逐渐稳定下来,达到新的平衡状态。软件开发具有明确的方向性。虽然在某些项目中可能并不明显,但它背后却缺乏这种进化,尽管如此,它仍然遵循着这种节奏。
For over a century after Charles Darwin introduced it, the standard model of evolution was that species changed gradually, somewhat steadily, over time. Suddenly, in the 1970s, this model was displaced by the “punctuated equilibrium” model. In this expanded view of evolution, long periods of gradual change or stability are interrupted by relatively short bursts of rapid change. Then things settle down into a new equilibrium. Software development has an intentional direction behind it that evolution lacks (although it may not be evident on some projects), but nonetheless it follows this kind of rhythm.
传统的重构描述听起来非常循序渐进。但真正能带来更深层次洞察的重构过程往往并非如此。一段持续改进模型的时期,可能会突然让你获得颠覆一切的洞见。这些突破并非每天都会发生,但许多最终构建出深刻模型和灵活设计的变革,都源于这些突破。
Classical descriptions of refactoring sound very steady. Refactoring toward deeper insight usually isn’t. A period of steady refinement of a model can suddenly bring you to an insight that shakes up everything. These breakthroughs don’t happen every day, yet a large proportion of the changes that lead to a deep model and supple design emerge from them.
这种情况往往看起来不像是一个机遇,而更像是一场危机。突然间,模型中出现了一些显而易见的缺陷。它在表达能力上存在巨大的漏洞,或者在某些关键领域存在晦涩难懂之处。甚至可能,它做出的论断完全错误。
Such a situation often does not look like an opportunity; it seems more like a crisis. Suddenly there is some obvious inadequacy in the model. There is a gaping hole in what it can express, or some critical area where it is opaque. Maybe it makes statements that are just wrong.
这意味着团队的理解水平已经提升到了一个新的层次。从他们如今更高的视角来看,旧模式显得很糟糕。从这个角度出发,他们可以构思出一个更好的模式。
This means the team has reached a new level of understanding. From their now-elevated viewpoint, the old model looks poor. From that viewpoint, they can conceive a far better one.
重构以求获得更深层次的洞察是一个持续的过程。隐含的概念被识别并显式化。部分设计变得更加灵活,或许会采用声明式风格。开发工作突然接近突破的边缘,并深入构建出一个深层次的模型——然后,稳步的改进又开始了。
Refactoring toward deeper insight is a continuing process. Implicit concepts are recognized and made explicit. Parts of the design are made suppler, perhaps taking on a declarative style. Development suddenly comes to the brink of a breakthrough and plunges through to a deep model—and then steady refinement starts again.
随着系统变得过于复杂,以至于无法在单个对象层面完全了解其运作,我们需要一些技术来操作和理解大型模型。本书的这一部分介绍了一些原则,这些原则使得建模过程能够扩展到非常复杂的领域。大多数此类决策必须在团队层面做出,甚至需要在团队之间协商。这些决策往往是设计与政治交织的领域。
As systems grow too complex to know completely at the level of individual objects, we need techniques for manipulating and comprehending large models. This part of the book presents principles that enable the modeling process to scale up to very complicated domains. Most such decisions must be made at team level or even negotiated between teams. These are the decisions where design and politics often intersect.
最具雄心的企业系统的目标是构建一个覆盖整个业务的紧密集成系统。然而,几乎所有此类组织的业务模式都过于庞大和复杂,无法作为一个整体进行管理甚至理解。因此,无论在概念上还是实现上,系统都必须被拆分成更小的部分。挑战在于如何在实现模块化的同时,又不损失集成优势,使系统的不同部分能够互操作,从而支持各种业务运营的协调。一个单一的、包罗万象的领域模型会显得笨重,并且充斥着难以察觉的重复和矛盾。而一组小型、独立的子系统通过临时接口连接在一起,则缺乏解决企业级问题的能力,并且会在每个集成点都出现一致性问题。通过系统化、不断演进的设计策略,可以避免这两种极端情况的弊端。
The goal of the most ambitious enterprise system is a tightly integrated system spanning the entire business. Yet the entire business model for almost any such organization is too large and complex to manage or even understand as a single unit. The system must be broken into smaller parts, in both concept and implementation. The challenge is to accomplish this modularity without losing the benefits of integration, allowing different parts of the system to interoperate to support the coordination of various business operations. A monolithic, all-encompassing domain model will be unwieldy and loaded with subtle duplications and contradictions. A set of small, distinct subsystems glued together with ad hoc interfaces will lack the power to solve enterprise-wide problems and allows consistency problems to arise at every integration point. The pitfalls of both extremes can be avoided with a systematic, evolving design strategy.
即使在如此庞大的规模下,领域驱动设计也无法生成与实现脱节的模型。每一个决策都必须对系统开发产生直接影响,否则就毫无意义。战略设计原则必须指导设计决策,以减少各部分之间的相互依赖性,提高清晰度,同时又不损失关键的互操作性和协同效应。这些原则必须使模型聚焦于捕捉系统的概念核心,即系统的“愿景”。而且,所有这些都必须在不拖慢项目进度的前提下完成。为了帮助实现这些目标,第四部分探讨了三个主要主题:背景、提炼和大规模结构。
Even at this scale, domain-driven design does not produce models unconnected to the implementation. Every decision must have a direct impact on system development, or else it is irrelevant. Strategic design principles must guide design decisions to reduce the interdependence of parts and improve clarity without losing critical interoperability and synergy. They must focus the model to capture the conceptual core of the system, the “vision” of the system. And they must do all this without bogging the project down. To help accomplish these goals, Part IV explores three broad themes: context, distillation, and large-scale structure.
上下文,这个最不显眼的原则,实际上却是最根本的。一个成功的模型,无论大小,都必须在逻辑上保持一致,不能出现相互矛盾或重叠的定义。企业系统有时会集成来源各异的子系统,或者应用场景截然不同,以至于该领域内几乎没有什么事物能被一概而论。因此,要统一这些分散部分中隐含的模型可能有些强人所难。通过明确定义一个有界上下文,就能解决这个问题。 通过确定模型的适用范围,并在必要时定义其与其他上下文的关系,建模者可以避免模型被扭曲。
Context, the least obvious of the principles, is actually the most fundamental. A successful model, large or small, has to be logically consistent throughout, without contradictory or overlapping definitions. Enterprise systems sometimes integrate subsystems with varying origins or have applications so distinct that very little in the domain is viewed in the same light. It may be asking too much to unify the models implicit in these disparate parts. By explicitly defining a BOUNDED CONTEXT within which a model applies and then, when necessary, defining its relationship with other contexts, the modeler can avoid bastardizing the model.
提炼可以减少冗余信息,并将注意力集中在恰当的位置。通常,人们会在领域内的次要问题上花费大量精力。整体领域模型需要突出系统中最具价值和最特殊的方面,并尽可能赋予这些方面最大的作用。虽然一些辅助组件至关重要,但必须将其置于恰当的位置。这种聚焦不仅有助于将精力集中在系统的关键部分,还能防止系统愿景的迷失。战略性提炼可以使庞大的模型变得清晰。而有了更清晰的视角,核心领域的设计就能更加有效。
Distillation reduces the clutter and focuses attention appropriately. Often a great deal of effort is spent on peripheral issues in the domain. The overall domain model needs to make prominent the most value-adding and special aspects of your system and be structured to give that part as much power as possible. While some supporting components are critical, they must be put into their proper perspective. This focus not only helps to direct efforts toward vital parts of the system, but it keeps the vision of the system from being lost. Strategic distillation can bring clarity to a large model. And with a clearer view, the design of the CORE DOMAIN can be made more useful.
大规模结构完善了整体图景。在非常复杂的模型中,你可能会只见树木不见森林。提炼有助于将注意力集中在核心部分,并将其他元素置于其辅助角色中,但如果没有一个统领全局的主题,应用一些系统级的设计元素和模式,这些关系仍然可能过于混乱。我将概述几种大规模结构的方法,然后深入探讨其中一种模式——责任层(RESPONSSIBILITY LAYERS) ,以探索使用这种结构的意义。这里讨论的具体结构只是示例,并非完整的目录。应根据需要创建新的结构,或者通过演化秩序的过程对现有结构进行修改。某些结构可以为设计带来统一性,从而加速开发并改进集成。
Large-scale structure completes the picture. In a very complex model, you may not see the forest for the trees. Distillation helps, by focusing the attention on the core and presenting the other elements in their supporting roles, but the relationships can still be too confusing without an overarching theme, applying some system-wide design elements and patterns. I’ll give an overview of a few approaches to large-scale structure and then go into depth on one such pattern, RESPONSIBILITY LAYERS, to explore the implications of using such a structure. The specific structures discussed are only examples; they are not a comprehensive catalog. New ones should be invented as needed, or these should be modified, through a process of EVOLVING ORDER. Some such structure can bring a uniformity to the design that accelerates development and improves integration.
这三项原则单独使用都很有用,但结合起来尤其强大,它们有助于设计出优秀的方案——即使是在一个庞大且无人完全理解的系统中。大规模的结构能够使不同的部分保持一致,从而促进它们之间的融合。结构和提炼使各部分之间复杂的关联变得易于理解,同时又能把握全局。有界上下文允许在不同的部分开展工作,而不会破坏模型或无意中将其分割。将这些概念添加到团队的通用语言中,有助于开发人员找到自己的解决方案。
These three principles, useful separately but particularly powerful taken together, help to produce good designs—even in a sprawling system that no one completely understands. Large-scale structure brings consistency to disparate parts to help those parts mesh. Structure and distillation make the complex relationships between parts comprehensible while keeping the big picture in view. BOUNDED CONTEXTS allow work to proceed in different parts without corrupting the model or unintentionally fragmenting it. Adding these concepts to the team’s UBIQUITOUS LANGUAGE helps developers work out their own solutions.
我曾经参与过一个项目,其中几个团队并行开发一个大型新系统。有一天,负责客户发票模块的团队准备实现一个名为“Charge”的对象时,发现另一个团队已经创建了一个。他们认真地着手复用这个现有对象。他们发现该对象缺少“费用代码”,于是添加了一个。它已经包含了他们需要的“已过账金额”属性。他们原本打算将其命名为“应付金额”,但——名字并不重要——他们最终还是改了名字。通过添加一些方法和关联,他们得到了一个符合预期且不影响原有功能的对象。虽然他们不得不忽略许多不需要的关联,但他们的应用程序模块最终运行正常。
I once worked on a project where several teams were working in parallel on a major new system. One day, the team working on the customer-invoicing module was ready to implement an object they called Charge, when they discovered that another team had already built one. Diligently, they set out to reuse the existing object. They discovered it didn’t have an “expense code,” so they added one. It already had the “posted amount” attribute they needed. They had been planning to call it “amount due,” but—what’s in a name?—they changed it. Adding a few more methods and associations, they got something that looked like what they wanted, without disturbing what was there. They had to ignore many associations they didn’t need, but their application module ran.
几天后,账单支付应用程序模块(该收费模块最初就是为此编写的)出现了神秘问题。出现了一些奇怪的收费项目,没有人记得输入过这些项目,而且这些收费项目也毫无道理。当使用某些功能时,程序开始崩溃,尤其是当使用“当月累计税款报告”功能时。调查显示,崩溃是由一个用于计算当月所有付款可抵扣金额的函数引起的。这些神秘记录的“可抵扣百分比”字段为空,尽管数据输入应用程序的验证要求填写该字段,甚至还设置了默认值。
A few days later, mysterious problems surfaced in the bill-payment application module for which the Charge had originally been written. Strange Charges appeared that no one remembered entering and that didn’t make any sense. The program began to crash when some functions were used, particularly the month-to-date tax report. Investigation revealed that the crash resulted when a function was used that summed up the amount deductible for all the current month’s payments. The mystery records had no value in the “percent deductible” field, although the validation of the data-entry application required it and even put in a default value.
问题在于这两个群体采用的是不同的模型,但他们并未意识到这一点,而且也没有任何机制来检测这种差异。各自对收费性质做出了在各自语境下适用的假设(例如,向客户收费与向供应商付款)。当这些代码在未解决这些矛盾的情况下被合并时,就导致了软件的不可靠性。
The problem was that these two groups had different models, but they did not realize it, and there were no processes in place to detect it. Each made assumptions about the nature of a charge that were useful in their context (billing customers versus paying vendors). When their code was combined without resolving these contradictions, the result was unreliable software.
如果他们当初能更清楚地认识到这一点,就能有意识地决定如何应对了。这或许意味着共同努力,制定一个通用模型,然后编写一套自动化测试套件来预防未来可能出现的意外情况。又或许意味着双方达成协议,各自开发不同的模型,互不干涉对方的代码。无论哪种方式,都始于对每个模型适用范围的明确界定。
If only they had been more aware of this reality, they could have consciously decided how to deal with it. That might have meant working together to hammer out a common model and then writing an automated test suite to prevent future surprises. Or it might simply have meant an agreement to develop separate models and keep hands off each other’s code. Either way, it starts with an explicit agreement on the boundaries within which each model applies.
他们发现问题后做了什么?他们创建了单独的客户费用和供应商费用类别,并根据相应团队的需求对每个类别进行了定义。眼下的问题解决了之后,他们又恢复了之前的做法。唉。
What did they do once they knew about the problem? They created separate Customer Charge and Supplier Charge classes and defined each according to the needs of the corresponding team. The immediate problem having been solved, they went back to doing things just as before. Oh well.
尽管我们很少会明确地思考这一点,但模型最基本的要求是内部一致性;也就是说,模型中的所有术语始终具有相同的含义,并且不包含任何相互矛盾的规则。模型内部的一致性,即每个术语都明确无误且规则之间互不矛盾,被称为统一性。如果模型在逻辑上不一致,那么它就毫无意义。在理想情况下,我们应该有一个涵盖企业所有领域的单一模型。这个模型应该是统一的,其中没有任何相互矛盾或重叠的术语定义。关于该领域的每一个逻辑陈述都应该是一致的。
Although we seldom think about it explicitly, the most fundamental requirement of a model is that it be internally consistent; that its terms always have the same meaning, and that it contain no contradictory rules. The internal consistency of a model, such that each term is unambiguous and no rules contradict, is called unification. A model is meaningless unless it is logically consistent. In an ideal world, we would have a single model spanning the whole domain of the enterprise. This model would be unified, without any contradictory or overlapping definitions of terms. Every logical statement about the domain would be consistent.
但大型系统开发的世界并非理想之地。要在整个企业系统中维持如此高的统一性,得不偿失。允许系统不同部分发展多种模型固然必要,但我们需要谨慎选择哪些部分可以发展差异,以及它们之间的关系。我们需要一些方法来保持模型关键部分的紧密统一。这一切并非自然而然或仅凭良好意愿就能实现,而只能通过有意识的设计决策和特定流程的建立来实现。对于大型系统而言,完全统一领域模型既不现实也不经济。
But the world of large systems development is not the ideal world. To maintain that level of unification in an entire enterprise system is more trouble than it is worth. It is necessary to allow multiple models to develop in different parts of the system, but we need to make careful choices about which parts of the system will be allowed to diverge and what their relationship to each other will be. We need ways of keeping crucial parts of the model tightly unified. None of this happens by itself or through good intentions. It happens only through conscious design decisions and institution of specific processes. Total unification of the domain model for a large system will not be feasible or cost-effective.
有时人们会抗拒这个事实。大多数人都能看到多模型带来的代价:它限制了集成,使沟通变得繁琐。此外,拥有多个模型似乎也显得不够优雅。这种对多模型的抵触情绪有时会导致人们雄心勃勃地尝试将大型项目中的所有软件统一到一个模型下。我知道我也曾犯过这种过分之想的错误。但请考虑其中的风险。
Sometimes people fight this fact. Most people see the price that multiple models exact by limiting integration and making communication cumbersome. On top of that, having more than one model somehow seems inelegant. This resistance to multiple models sometimes leads to very ambitious attempts to unify all the software in a large project under a single model. I know I’ve been guilty of this kind of overreaching. But consider the risks.
1.可能同时尝试替换太多旧系统。
1. Too many legacy replacements may be attempted at once.
2.大型项目可能会因为协调工作量超过其能力而陷入停滞。
2. Large projects may bog down because the coordination overhead exceeds their abilities.
3.有特殊需求的应用可能不得不使用无法完全满足其需求的模型,迫使它们将行为放在其他地方。
3. Applications with specialized requirements may have to use models that don’t fully satisfy their needs, forcing them to put behavior elsewhere.
4.相反,试图用单一模型来满足所有人的需求可能会导致复杂的选项,从而使模型难以使用。
4. Conversely, attempting to satisfy everyone with a single model may lead to complex options that make the model difficult to use.
此外,模型差异不仅可能源于技术问题,也可能源于政治分裂和管理优先事项的不同。团队组织和开发流程也可能导致不同模型的出现。因此,即使没有技术因素阻碍完全整合,项目仍然可能面临多种模型。
What’s more, model divergences are as likely to come from political fragmentation and differing management priorities as from technical concerns. And the emergence of different models can be a result of team organization and development process. So even when no technical factor prevents full integration, the project may still face multiple models.
鉴于为整个企业维持统一的模型并不现实,我们不必听天由命。通过积极主动地决定哪些部分应该统一,并务实地认识到哪些部分尚未统一,我们可以构建一个清晰、一致的现状图景。有了这份图景,我们就可以着手确保那些需要统一的部分保持统一状态,同时避免那些尚未统一的部分造成混乱或腐败。
Given that it isn’t feasible to maintain a unified model for an entire enterprise, we don’t have to leave ourselves at the mercy of events. Through a combination of proactive decisions about what should be unified and pragmatic recognition of what is not unified, we can create a clear, shared picture of the situation. With that in hand, we can set about making sure that the parts we want to unify stay that way, and the parts that are not unified don’t cause confusion or corruption.
我们需要一种方法来标明不同模型之间的界限和关系。我们需要有意识地选择策略,然后始终如一地执行该策略。
We need a way to mark the boundaries and relationships between different models. We need to choose our strategy consciously and then follow our strategy consistently.
本章阐述了识别、沟通和选择模型及其关系局限性的技巧其他方面。一切都始于绘制项目现状图。限定上下文定义了每个模型的适用范围,而上下文图则提供了项目上下文及其相互关系的全局概览。这种减少歧义本身就会改变项目的运作方式,但这还不够。一旦我们确定了限定上下文,持续集成过程将确保模型的统一性。
This chapter lays out techniques for recognizing, communicating, and choosing the limits of a model and its relationships to others. It all starts with mapping the current terrain of the project. A BOUNDED CONTEXT defines the range of applicability of each model, while a CONTEXT MAP gives a global overview of the project’s contexts and the relationships between them. This reduction of ambiguity will, in and of itself, change the way things happen on the project, but it isn’t necessarily enough. Once we have a CONTEXT BOUNDED, a process of CONTINUOUS INTEGRATION will keep the model unified.
然后,从这种稳定的情况出发,我们可以开始向更有效的边界上下文及其关联策略过渡,从具有共享内核的紧密联盟上下文到各自独立发展的松散耦合模型。
Then, starting from this stable situation, we can start to migrate toward more effective strategies for BOUNDING CONTEXTS and relating them, ranging from closely allied contexts with SHARED KERNELS to loosely coupled models that go their SEPARATE WAYS.
图 14.1. 模型完整性模式的导航图
Figure 14.1. A navigation map for model integrity patterns
细胞之所以能够存在,是因为它们的细胞膜界定了物质的进出,并决定了物质可以通过的物质。
Cells can exist because their membranes define what is in and out and determine what can pass.
大型项目中往往存在多种模型并存的情况,而且在很多情况下都能正常运作。不同的模型适用于不同的场景。例如,你可能需要将新软件与团队无法控制的外部系统集成。这种情况对于每个人来说都很清楚,因为这是一个截然不同的场景,正在开发的模型并不适用。但其他情况则可能更加模糊和令人困惑。在本章开头的案例中,两个团队正在为同一个新系统开发不同的功能。他们使用的是同一个模型吗?他们的初衷是至少共享部分工作内容,但并没有明确的界限来区分哪些内容需要共享,哪些内容需要单独共享。而且,他们也没有任何流程来维护共享模型的一致性,或者快速检测分歧。直到系统行为突然变得难以预测之后,他们才意识到彼此已经分道扬镳。
Multiple models coexist on big projects, and this works fine in many cases. Different models apply in different contexts. For example, you may have to integrate your new software with an external system over which your team has no control. A situation like this is probably clear to everyone as a distinct context where the model under development doesn’t apply, but other situations can be more vague and confusing. In the story that opened this chapter, two teams were working on different functionality for the same new system. Were they working on the same model? Their intention was to share at least part of what they did, but there was no demarcation to tell them what they did or did not share. And they had no process in place to hold a shared model together or quickly detect divergences. They realized they had diverged only after their system’s behavior suddenly became unpredictable.
即使是同一个团队,最终也可能存在多个模型。沟通不畅会导致对模型的解读出现细微的冲突。较早的代码通常反映了早期对模型的理解,而这种理解与当前模型略有不同。
Even a single team can end up with multiple models. Communication can lapse, leading to subtly conflicting interpretations of the model. Older code often reflects an earlier conception of the model that is subtly different from the current model.
大家都知道另一个系统的数据格式不同,需要进行数据转换,但这只是问题的机械层面。更根本的差异在于……这两个系统都隐含着不同的模型。当差异并非来自外部系统,而是来自同一代码库内部时,就更难被发现。然而,这种情况在所有大型团队项目中都会发生。
Everyone is aware that the data format of another system is different and calls for a data conversion, but this is only the mechanical dimension of the problem. More fundamental is the difference in the models implicit in the two systems. When the discrepancy is not with an external system, but within the same code base, it is even less likely to be recognized. Yet this happens on all large team projects.
任何大型项目都会用到多种模型。然而,当基于不同模型的代码混杂在一起时,软件就会变得漏洞百出、不可靠且难以理解。团队成员之间的沟通也会变得混乱。而且,通常很难明确在什么情况下 不 应该使用某个模型。
Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand. Communication among team members becomes confused. It is often unclear in what context a model should not be applied.
最终,当运行中的代码出现故障时,问题就会暴露出来,但问题的根源在于团队的组织方式和人员的互动方式。因此,为了厘清模型的上下文,我们必须同时考察项目及其最终产品(代码、数据库模式等等)。
Failure to keep things straight is ultimately revealed when the running code doesn’t work right, but the problem starts in the way teams are organized and the way people interact. Therefore, to clarify the context of a model, we have to look at both the project and its end products (code, database schemas, and so on).
模型适用于特定的上下文。上下文可以是代码的特定部分,也可以是特定团队的工作。对于在头脑风暴会议中创建的模型,其上下文可能仅限于那次特定的讨论。本书示例中使用的模型的上下文是该示例章节及其后续讨论。模型上下文是指为了使模型中的术语具有特定含义而必须满足的所有条件。
A model applies in a context. The context may be a certain part of the code, or the work of a particular team. For a model invented in a brainstorming session, the context could be limited to that particular conversation. The context of a model used in an example in this book is that particular example section and any later discussion of it. The model context is whatever set of conditions must apply in order to be able to say that the terms in a model have a specific meaning.
要着手解决多模型问题,我们需要明确定义特定模型的适用范围,将其定义为软件系统中一个界限分明的部分,在该部分内只应用一个模型,并尽可能保持模型的统一性。这一定义必须与团队组织结构相协调。
To begin to solve the problems of multiple models, we need to define explicitly the scope of a particular model as a bounded part of a software system within which a single model will apply and will be kept as unified as possible. This definition has to be reconciled with the team organization.
所以:
Therefore:
明确定义模型适用的上下文。明确设定模型的边界,包括团队组织结构、在应用程序特定部分的使用情况以及代码库和数据库模式等物理表现形式。在这些边界内严格保持模型的一致性,但不要被边界之外的问题所干扰或迷惑。
Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Keep the model strictly consistent within these bounds, but don’t be distracted or confused by issues outside.
限定上下文能够界定特定模型的适用范围,使团队成员对哪些内容必须保持一致以及它与其他上下文的关系有清晰且共同的理解。在该上下文范围内,应努力保持模型的逻辑统一,但无需担心其在范围之外的适用性。在其他上下文中,适用其他模型,这些模型在术语、概念、规则以及通用语言的方言方面存在差异。通过明确划定边界,您可以保持模型的纯粹性,从而确保其在适用范围内有效运作。同时,当您将注意力转移到其他情境时,也能避免混淆。跨越边界的整合必然涉及一些转换,您可以对此进行明确的分析。
A BOUNDED CONTEXT delimits the applicability of a particular model so that team members have a clear and shared understanding of what has to be consistent and how it relates to other CONTEXTS. Within that CONTEXT, work to keep the model logically unified, but do not worry about applicability outside those bounds. In other CONTEXTS, other models apply, with differences in terminology, in concepts and rules, and in dialects of the UBIQUITOUS LANGUAGE. By drawing an explicit boundary, you can keep the model pure, and therefore potent, where it is applicable. At the same time, you avoid confusion when shifting your attention to other CONTEXTS. Integration across the boundaries necessarily will involve some translation, which you can analyze explicitly.
一家航运公司正在进行一个内部项目,开发一款新的货物预订应用程序。该应用程序将基于对象模型。那么,该模型适用的限界上下文是什么?要回答这个问题,我们需要了解项目的实际情况。请注意,这里指的是项目的现状,而不是理想状态。
A shipping company has an internal project to develop a new application for booking cargo. This application is to be driven by an object model. What is the BOUNDED CONTEXT within which this model applies? To answer this question, we have to look at what is happening on the project. Keep in mind, this is a look at the project as it is, not as it ideally should be.
一个项目团队正在开发预订应用程序本身。他们无需修改模型对象,但他们构建的应用程序必须显示和操作这些对象。该团队是模型的使用者。模型在其应用程序(主要使用者)内部有效,因此预订应用程序符合模型边界要求。
One project team is working on the booking application itself. They are not expected to modify the model objects, but the application they are building has to display and manipulate those objects. This team is a consumer of the model. The model is valid within the application (its primary consumer), and therefore the booking application is in bounds.
已完成的预订必须传输到原有的货物跟踪系统。事先已决定新模型将与原有模型有所不同,因此原有货物跟踪系统位于边界之外。新模型与原有系统之间的必要转换将由原有系统维护团队负责。转换机制并非由模型驱动,也不在有界上下文中。(它本身就是边界的一部分,这将在上下文映射中讨论。)转换工作独立于上下文之外(不基于模型)是件好事。要求原有系统团队实际使用模型是不现实的,因为他们的主要工作独立于上下文之外。
The completed bookings have to be passed to the legacy cargotracking system. A decision was made up front that the new model would depart from that of the legacy, so the legacy cargotracking system is outside the boundary. Necessary translation between the new model and the legacy is to be the responsibility of the legacy maintenance team. The translation mechanism is not driven by the model. It is not in the BOUNDED CONTEXT. (It is part of the boundary itself, which will be discussed in CONTEXT MAP.) It is good that translation is out of CONTEXT (not based on the model). It would be unrealistic to ask the legacy team to make any real use of the model because their primary work is out of CONTEXT.
负责模型的团队处理每个对象的整个生命周期,包括持久化。由于该团队掌控着数据库模式,他们一直刻意保持对象关系映射的简洁性。换句话说,模式由模型驱动,因此符合规范。
The team responsible for the model deals with the whole life cycle of each object, including persistence. Because this team has control of the database schema, they’ve been deliberately keeping the object-relational mapping straightforward. In other words, the schema is being driven by the model and therefore is in bounds.
另一个团队正在开发一个用于调度货船航次的模型和应用程序。调度和订舱团队是同时成立的,两个团队都希望开发一个统一的系统。这两个团队之间有过一些非正式的协调,偶尔也会共享一些对象,但并没有系统地进行共享。他们并非在同一个有界上下文(BOUNDED CONTEXT)中工作。这存在风险,因为他们并没有意识到自己是在开发独立的模型。除非他们建立相应的流程来管理这种情况,否则在进行集成时必然会出现问题。(本章稍后将讨论的共享内核(SHARED KERNEL)可能是一个不错的选择。)然而,第一步是要认清现状。他们不在同一个上下文中,在做出一些改变之前,应该停止尝试共享代码。
Yet another team is working on a model and application for scheduling the voyages of the cargo ships. The scheduling and booking teams were initiated together, and both teams had intended to produce a single, unified system. The two teams have casually coordinated with each other, and they occasionally share objects, but they are not systematic about it. They are not working in the same BOUNDED CONTEXT. This is a risk, because they do not think of themselves as working on separate models. To the extent they integrate, there will be problems unless they put in place processes to manage the situation. (The SHARED KERNEL, discussed later in this chapter, might be a good choice.) The first step, though, is to recognize the situation as it is. They are not in the same CONTEXT and should stop trying to share code until some changes are made.
这个有界上下文由系统中所有受此特定模型驱动的方面组成:模型对象、存储模型对象的数据库模式以及预订应用程序。主要有两个团队在此上下文中工作:建模团队和应用程序团队。信息需要与旧版跟踪系统交换,旧版团队主要负责在此边界处进行转换,建模团队则负责协助。预订模型和航程计划模型之间没有明确定义的关系,定义这种关系应该是这些团队的首要任务之一。同时,他们应该非常谨慎地共享代码或数据。
This BOUNDED CONTEXT is made up of all those aspects of the system that are driven by this particular model: the model objects, the database schema that persists the model objects, and the booking application. Two teams work primarily in this CONTEXT: the modeling team and the application team. Information has to be exchanged with the legacy tracking system, and the legacy team has primary responsibility for the translation at this boundary, with cooperation from the modeling team. There is no clearly defined relationship between the booking model and the voyage schedule model, and defining that relationship should be one of those teams’ first actions. In the meantime, they should be very careful about sharing code or data.
那么,定义这个“有界上下文”带来了什么好处呢?对于在上下文中工作的团队来说:清晰度。这两个团队知道他们必须坚持使用同一个模型。他们基于这种认知做出设计决策,并密切关注可能出现的偏差。对于上下文之外的团队来说:自由度。他们不必再游走在灰色地带,不用使用相同的模型,却又感觉自己应该使用。但在这个特定案例中最切实的好处或许在于,他们意识到了预订模型团队和航程安排团队之间非正式共享的风险。为了避免出现问题,他们需要认真权衡共享的成本和收益,并制定相应的流程来确保共享的有效性。而这一切的前提是,每个人都必须理解模型上下文的界限在哪里。
So, what has been gained by defining this BOUNDED CONTEXT? For the teams working in CONTEXT: clarity. Those two teams know they must stay consistent with one model. They make design decisions in that knowledge and watch for fractures. For the teams outside: freedom. They don’t have to walk in the gray zone, not using the same model, yet somehow feeling they should. But the most concrete gain in this particular case is probably realizing the risk of the informal sharing between the booking model team and the voyage schedule team. To avoid problems, they really need to decide on the cost/benefit trade-offs of sharing and put in processes to make it work. This won’t happen unless everyone understands where the bounds of the model contexts are.
当然,边界是特殊的区域。有界上下文与其邻近上下文之间的关系需要谨慎对待。上下文地图描绘了这片区域,展现了上下文及其联系的全貌,而多种模式则定义了上下文之间各种关系的本质。持续整合的过程则确保了模型在有界上下文中的统一性。
Of course, boundaries are special places. The relationships between a BOUNDED CONTEXT and its neighbors require care and attention. The CONTEXT MAP charts the territory, giving the big picture of the CONTEXTS and their connections, while several patterns define the nature of the various relationships between CONTEXTS. And a process of CONTINUOUS INTEGRATION preserves unity of the model within a BOUNDED CONTEXT.
但在深入探讨所有这些之前,当一个模型的统一性出现问题时,会呈现出什么样的特征?如何识别概念上的分裂?
But before proceeding to all that, what does it look like when unification of a model is breaking down? How do you recognize conceptual splinters?
许多症状可能表明存在未被识别的模型差异。最明显的症状之一是编码接口不匹配。更隐蔽的是,意外行为也可能是一个征兆。持续集成流程结合自动化测试有助于发现这类问题。但早期预警通常是语言上的混淆。
Many symptoms may indicate unrecognized model differences. Some of the most obvious are when coded interfaces don’t match up. More subtly, unexpected behavior is a likely sign. The CONTINUOUS INTEGRATION process with automated tests can help catch these kinds of problems. But the early warning is usually a confusion of language.
将不同模型的元素组合在一起会导致两类问题:概念重复和假同源词。概念重复意味着存在两个模型元素(及其相应的实现)实际上代表同一个概念。每次信息发生变化时,都需要在两个地方进行转换并更新。每次新知识导致其中一个对象发生变化时,另一个对象也必须重新分析并进行相应的更改。然而,实际上并不会进行这种重新分析,因此最终会得到两个遵循不同规则甚至包含不同数据的同一概念版本。此外,团队成员不仅需要学习两种实现同一功能的方法,还需要学习所有相关的同步方式。
Combining elements of distinct models causes two categories of problems: duplicate concepts and false cognates. Duplication of concepts means that there are two model elements (and attendant implementations) that actually represent the same concept. Every time this information changes, it has to be updated in two places with conversions. Every time new knowledge leads to a change in one of the objects, the other has to be reanalyzed and changed too. Except the reanalysis doesn’t happen in reality, so the result is two versions of the same concept that follow different rules and even have different data. On top of that, the team members must learn not one but two ways of doing the same thing, along with all the ways they are being synchronized.
假同源词虽然不太常见,但危害却更隐蔽。这种情况指的是,两个人使用同一个术语(或已实现的对象)时,误以为他们在谈论同一件事,但实际上并非如此。本章开头的例子(两种不同的业务活动都称为“收费”)就是一个典型的例子,但当两个定义实际上与领域内的同一方面相关,只是概念化方式略有不同时,冲突可能会更加微妙。假同源词会导致开发团队互相抄袭代码,数据库也会出现问题。团队内部沟通存在奇怪的矛盾和混乱。“假同源词”这个术语通常用于自然语言。例如,英语母语者学习西班牙语时经常误用“embarazada ”这个词。这个词的意思不是“尴尬”,而是“怀孕”。哎呀。
False cognates may be slightly less common, but more insidiously harmful. This is the case when two people who are using the same term (or implemented object) think they are talking about the same thing, but really are not. The example in the beginning of this chapter (two different business activities both called Charge) is typical, but conflicts can be even subtler when the two definitions are actually related to the same aspect in the domain, but have been conceptualized in slightly different ways. False cognates lead to development teams that step on each other’s code, databases that have weird contradictions, and confusion in communication within the team. The term false cognate is ordinarily applied to natural languages. For example, English speakers learning Spanish often misuse the word embarazada. This word does not mean “embarrassed”; it means “pregnant.” Oops.
当您发现这些问题时,您的团队需要做出决定。您可能需要重新整合模型并改进流程以防止碎片化。或者,碎片化可能是由于各个团队出于正当理由希望将模型朝着不同方向发展造成的,在这种情况下,您可以决定允许他们独立发展。本章剩余部分将探讨如何处理这些问题。
When you detect these problems, your team will have to make a decision. You may want to pull the model back together and refine the processes to prevent fragmentation. Or the fragmentation may be a result of groups who want to pull the model in different directions for good reasons, and you may decide to let them develop independently. Dealing with these issues is the subject of the remaining patterns in this chapter.
Having defined a BOUNDED CONTEXT, we must keep it sound.
当多人同时在同一个有限情境(BOUNDED CONTEXT)中工作时,模型很容易出现碎片化。团队规模越大,问题也越大,但即使只有三四个人,也可能遇到严重问题。然而,将系统不断细分为更小的情境(CONTEXT),最终会丧失宝贵的整合性和连贯性。
When a number of people are working in the same BOUNDED CONTEXT, there is a strong tendency for the model to fragment. The bigger the team, the bigger the problem, but as few as three or four people can encounter serious problems. Yet breaking down the system into ever-smaller CONTEXTS eventually loses a valuable level of integration and coherency.
有时,开发者并不完全理解他人建模的对象或交互的意图,他们对其进行修改,使其无法用于原用途。有时,他们没有意识到自己正在处理的概念已经体现在模型的其他部分,于是(不精确地)复制了这些概念和行为。有时,他们虽然知道这些其他表达方式的存在,但害怕修改它们会破坏现有功能,因此也选择了复制概念和功能。
Sometimes developers do not fully understand the intent of some object or interaction modeled by someone else, and they change it in a way that makes it unusable for its original purpose. Sometimes they don’t realize that the concepts they are working on are already embodied in another part of the model and they duplicate (inexactly) those concepts and behavior. Sometimes they are aware of those other expressions but are afraid to tamper with them, for fear of corrupting the existing functionality, and so they proceed to duplicate concepts and functionality.
要维持开发任何规模的统一系统所需的沟通水平都非常困难。我们需要提高沟通效率并降低复杂性的方法。我们还需要安全机制来防止过度谨慎的行为,例如开发人员因为害怕破坏现有代码而重复编写功能。
It is very hard to maintain the level of communication needed to develop a unified system of any size. We need ways of increasing communication and reducing complexity. We also need safety nets that prevent overcautious behavior, such as developers duplicating functionality because they are afraid they will break existing code.
正是在这种环境下,极限编程(XP)才能真正发挥作用。许多XP实践都旨在解决这样一个特定问题:如何在多人不断修改的情况下维护设计的一致性。纯粹的XP非常适合在单一的限界上下文中维护模型的完整性。然而,无论是否使用XP,持续集成流程都至关重要。
It is in this environment that Extreme Programming (XP) really comes into its own. Many XP practices are aimed at this specific problem of maintaining a coherent design that is being constantly changed by many people. XP in its purest form is a nice fit for maintaining model integrity within a single BOUNDED CONTEXT. However, whether or not XP is being used, it is essential to have some process of CONTINUOUS INTEGRATION.
持续集成意味着上下文中的所有工作都会被合并,并保持足够频繁的一致性,以便在出现分歧时能够迅速发现并纠正。与领域驱动设计中的其他所有内容一样,持续集成也包含两个层面:(1)模型概念的集成;(2)实现的集成。
CONTINUOUS INTEGRATION means that all work within the context is being merged and made consistent frequently enough that when splinters happen they are caught and corrected quickly. CONTINUOUS INTEGRATION, like everything else in domain-driven design, operates at two levels: (1) the integration of model concepts and (2) the integration of the implementation.
团队成员之间通过持续沟通来整合概念。团队必须培养对不断变化的模型的共同理解。许多实践方法都有所帮助,但最根本的是不断完善通用语言。同时,实现工件通过系统的合并/构建/测试流程进行整合,该流程可以及早发现模型中的缺陷。虽然有很多集成流程,但大多数有效的流程都具有以下特征:
Concepts are integrated by constant communication among team members. The team must cultivate a shared understanding of the ever-changing model. Many practices help, but the most fundamental is constantly hammering out the UBIQUITOUS LANGUAGE. Meanwhile, the implementation artifacts are being integrated by a systematic merge/build/test process that exposes model splinters early. Many processes for integration are used, but most of the effective ones share these characteristics:
• 一种循序渐进、可复现的合并/构建技术;
• A step-by-step, reproducible merge/build technique;
• 自动化测试套件;以及
• Automated test suites; and
• 为未集成变更的生命周期设定一个合理的较小上限的规则。
• Rules that set some reasonably small upper limit on the lifetime of unintegrated changes.
有效流程的另一面,虽然很少正式纳入考虑,是概念整合。
The other side of the coin in effective processes, although it is seldom formally included, is conceptual integration.
•在模型和应用讨论中不断运用通用语言
• Constant exercise of the UBIQUITOUS LANGUAGE in discussions of the model and application
大多数敏捷项目至少每天都会合并每个开发人员的代码变更。合并频率可以根据变更速度进行调整,只要确保任何未集成的变更在其他团队成员进行大量不兼容的工作之前就被合并即可。
Most Agile projects have at least daily merges of each developer’s code changes. The frequency can be adjusted to the pace of change, as long as any unintegrated change would be merged before a significant amount of incompatible work could be done by other team members.
在模型驱动设计中,概念的整合为实施的整合铺平了道路,而实施的整合则证明了模型的有效性和一致性,并暴露了其中的不足。
In a MODEL-DRIVEN DESIGN, the integration of concepts smooths the way for the integration of the implementation, while the integration of the implementation proves the validity and consistency of the model and exposes splinters.
所以:
Therefore:
建立一套流程,定期合并所有代码和其他实现工件,并利用自动化测试快速发现碎片化问题。随着不同人员对概念的理解不断演变,要坚持不懈地运用通用语言,最终达成对模型的共识。
Institute a process of merging all code and other implementation artifacts frequently, with automated tests to flag fragmentation quickly. Relentlessly exercise the UBIQUITOUS LANGUAGE to hammer out a shared view of the model as the concepts evolve in different people’s heads.
最后,不要把工作搞得比实际需要的更大。持续整合只有在有限的上下文中才至关重要。涉及相邻上下文的设计问题,包括翻译,不必以相同的速度处理。
Finally, do not make the job any bigger than it has to be. CONTINUOUS INTEGRATION is essential only within a BOUNDED CONTEXT. Design issues involving neighboring CONTEXTS, including translation, don’t have to be dealt with at the same pace.
持续集成适用于任何大于两人任务的独立限界上下文。它能维护该单一模型的完整性。当多个限界上下文共存时,您需要确定它们之间的关系并设计必要的接口。
CONTINUOUS INTEGRATION would be applied within any individual BOUNDED CONTEXT that is larger than a two-person task. It maintains the integrity of that single model. When multiple BOUNDED CONTEXTS coexist, you have to decide on their relationships and design any necessary interfaces. . . .
个体的有限语境仍然无法提供全局视角。其他模型的背景可能仍然模糊不清,并且处于不断变化之中。
An individual BOUNDED CONTEXT still does not provide a global view. The context of other models may still be vague and in flux.
其他团队成员可能不太清楚上下文的界限,会在不知不觉中做出一些改动,模糊边界或使相互联系变得复杂。当需要在不同的上下文之间建立联系时,它们往往会相互渗透。
People on other teams won’t be very aware of the CONTEXT bounds and will unknowingly make changes that blur the edges or complicate the interconnections. When connections must be made between different contexts, they tend to bleed into each other.
在有界上下文之间重用代码是一个需要避免的风险。功能和数据的集成必须经过转换。您可以通过定义不同上下文之间的关系,并创建项目中所有模型上下文的全局视图来减少混淆。
Code reuse between BOUNDED CONTEXTS is a hazard to be avoided. Integration of functionality and data must go through a translation. You can reduce confusion by defining the relationship between the different contexts and creating a global view of all the model contexts on the project.
上下文地图介于项目管理和软件设计之间。其边界自然会随着团队组织的轮廓而变化。密切合作的人员自然会共享一个模型上下文。不同团队的成员,或者即使在同一团队中但彼此不交流的成员,也会各自构建不同的上下文。办公空间也会产生影响,因为位于同一栋楼两端的团队成员——更不用说位于不同城市的团队成员——如果没有额外的整合措施,很可能会各自为政。大多数项目经理凭直觉就能意识到这些因素,并大致按照子系统来组织团队。但是,团队组织与软件模型和设计之间的相互关系仍然没有得到足够的重视。管理者和团队成员都需要进一步了解这一点。需要对软件模型和设计的持续概念细分有清晰的了解。
A CONTEXT MAP is in the overlap between project management and software design. The natural course of events is for the boundaries to follow the contours of team organization. People who work closely will naturally share a model context. People on different teams, or those that don’t talk, even if they are on the same team, will split off into different contexts. Physical office space can have an impact too, as team members on opposite ends of a building—not to mention different cities—will probably diverge without extra integration effort. Most project managers intuitively recognize these factors and broadly organize teams around subsystems. But the interrelationship between team organization and software model and design is still not prominent enough. Both managers and team members need a clear view into the ongoing conceptual subdivision of the software model and design.
所以:
Therefore:
识别项目中使用的每个模型,并定义其有界上下文。这包括非面向对象子系统的隐式模型。为每个有界上下文命名,并使这些名称成为通用语言的一部分。
Identify each model in play on the project and define its BOUNDED CONTEXT. This includes the implicit models of non-object-oriented subsystems. Name each BOUNDED CONTEXT, and make the names part of the UBIQUITOUS LANGUAGE.
描述模型之间的接触点,明确任何沟通的翻译,并重点说明任何共享之处。
Describe the points of contact between the models, outlining explicit translation for any communication and highlighting any sharing.
先绘制 现有 地形图,改造计划稍后再考虑。
Map the existing terrain. Take up transformations later.
在每个限定上下文中,您将拥有一个连贯的通用语言方言。限定上下文的名称本身也会进入该语言,这样您就可以通过明确上下文来清晰地描述设计的任何部分的模型。
Within each BOUNDED CONTEXT, you will have a coherent dialect of the UBIQUITOUS LANGUAGE. The names of the BOUNDED CONTEXTS will themselves enter that LANGUAGE so that you can speak unambiguously about the model of any part of the design by making your CONTEXT clear.
项目地图(MAP)无需采用任何特定形式记录。我发现像本章中的图表有助于可视化和沟通项目地图。其他人可能更喜欢文字描述或其他图形表示方式。在某些情况下,团队成员之间的讨论就足够了。详细程度可以根据需要而定。无论项目地图采用何种形式,都必须让项目中的每个人都能理解和使用。它必须为每个“限定上下文”(BOUNDED CONTENT)提供清晰的名称,并且必须明确各个联系点及其性质。
The MAP does not have to be documented in any particular form. I find diagrams like the ones in this chapter to be helpful in visualizing and communicating the map. Others may prefer a more textual description or a different graphical representation. In some situations, discussion among teammates may be sufficient. The level of detail can vary according to need. Whatever form the MAP takes, it must be shared and understood by everyone on the project. It must provide a clear name for each BOUNDED CONTEXT, and it must make the points of contact and their natures clear.
有界上下文之间的关系形式多种多样,取决于设计问题和项目组织问题。本章稍后将阐述适用于不同情况的各种上下文关系模式,这些模式可以为描述您在自己的上下文映射图中发现的关系提供术语。请记住,上下文映射图始终反映的是现状,因此您最初发现的关系可能并不符合这些模式。如果关系接近,您可以考虑使用模式名称,但不要强行使用。只需描述您发现的关系即可。之后,您可以逐步过渡到更标准化的关系。
The relationships between BOUNDED CONTEXTS take many forms depending on both design issues and project organizational issues. Later, this chapter will lay out various patterns of relationships between CONTEXTS that are effective in different situations, and that can provide terms to describe the relationships you find in your own MAP. Keeping in mind that the CONTEXT MAP always represents the situation as it stands, the relationships you find may not fit these patterns initially. If they fall close, you may wish to use the pattern name, but don’t force it. Just describe the relationships you find. Later you can begin to migrate toward more standardized relationships.
那么,如果你发现了一个“裂痕”——一个完全纠缠在一起但又包含矛盾的模型,该怎么办呢?在地图上画一条龙,然后把所有内容都描述清楚。接着,凭借准确的全局视角,解决那些令人困惑的地方。较小的裂痕可以修复,并且可以建立相应的流程来加固它。如果某个关系模糊不清,你可以选择最接近的模式并朝着它前进。你的首要任务是构建一个清晰的“上下文地图”,这可能意味着要修复你发现的实际问题。但不要让这种必要的修复导致彻底的重组。在你拥有一个明确的“上下文地图”,将所有工作置于一个有界的上下文中,并明确所有相关模型之间的关系之前,只修改那些明显的矛盾之处。
So, what do you do if you’ve discovered a splinter—a model that is completely entangled but contains inconsistencies? Put a dragon on the map and finish describing everything. Then, with an accurate global view, address the points of confusion. A minor splinter can be repaired, and processes can be put in place to shore it up. If a relationship is vague, you can choose the nearest pattern and move toward it. Your first order of business is to arrive at a clear CONTEXT MAP, and this may mean fixing real problems you have found. But don’t let this necessary repair lead to wholesale reorganization. Until you have an unambiguous CONTEXT MAP that places all your work into some BOUNDED CONTEXT, with explicit relationships between all connected models, change only the outright contradictions.
一旦你有了清晰的情境图,你就会发现需要改进的地方。你可以对团队组织或设计进行深思熟虑的调整。记住,在现实发生改变之前,不要修改情境图。
Once you have a coherent CONTEXT MAP, you’ll see things you want to change. You can make considered changes to the organization of teams or to the design. Remember, don’t change the map until the change in reality is done.
我们再次回到货运系统。该应用程序的主要功能之一是在预订时自动规划货物路线。其模型大致如下:
We return again to the shipping system. One of the application’s major features was to be the automatic routing of cargos at booking time. The model was something like this:
图 14.2
Figure 14.2
路由服务是一项服务 它封装了一个机制,该机制位于一个由无副作用函数组成的意图揭示接口背后。这些函数的结果用断言来描述。
The Routing Service is a SERVICE that encapsulates a mechanism behind an INTENTION-REVEALING INTERFACE made up of SIDEEFFECT-FREE FUNCTIONS. The results of those functions are characterized with ASSERTIONS.
1.该接口声明,当传入路线规范时,将返回行程单。
1. The interface declares that when a Route Specification is passed in, an Itinerary will be returned.
2.断言表明返回的行程将满足传入的路线规范。
2. The ASSERTION states that the returned Itinerary will satisfy the Route Specification that was passed in.
关于这项极其艰巨的任务是如何完成的,文中并未提及。现在,让我们揭开帷幕,一探究竟。
Nothing is stated about how this very difficult task is performed. Now let’s go behind the curtain to see the mechanism.
最初,在这个示例所基于的项目中,我对路由服务的内部机制过于固执己见。我希望实际的路由操作能够使用一个扩展的领域模型来完成,该模型能够表示船舶航程,并将其与行程中的航段直接关联起来。但是,负责路由问题的团队指出,为了使其性能良好并利用成熟的算法,解决方案需要实现为一个优化的网络,其中航程的每个航段都表示为矩阵中的一个元素。他们坚持为此目的使用一个独立的航运操作模型。
Initially on the project on which this example is based, I was too dogmatic about the internals of the Routing Service. I wanted the actual routing operation to be done with an extended domain model that would represent vessel voyages and directly relate them to the Legs in the Itinerary. But the team working on the routing problem pointed out that, to make it perform well and to draw on well-established algorithms, the solution needed to be implemented as an optimized network, with each leg of a voyage represented as an element in a matrix. They insisted on a distinct model of shipping operations for this purpose.
显然,他们对当时设计的路由流程的计算需求判断是正确的,因此,由于没有更好的办法,我只好妥协。实际上,我们创建了两个独立的有界上下文,每个上下文都有其自身的运输操作概念组织。(参见图 14.3。)
They were clearly right about the computational demands of the routing process as then designed, and so, lacking any better idea, I yielded. In effect, we created two separate BOUNDED CONTEXTS, each of which had its own conceptual organization of shipping operations. (See Figure 14.3.)
图 14.3.为实现高效路由算法,形成了两个有界上下文
Figure 14.3. Two BOUNDED CONTEXTS formed to allow efficient routing algorithms to be applied
我们的要求是接收路由服务请求,将其转换为网络遍历服务可以理解的术语,然后将结果转换为路由服务期望给出的形式。
Our requirement was to take a Routing Service request, translate it into terms the Network Traversal Service could understand, then take the result and translate it into the form a Routing Service is expected to give.
这意味着无需映射这两个模型中的所有内容,只需进行两项特定的转换即可:
This means it was not necessary to map everything in these two models, but only to be able to make two specific translations:
路线规范→位置代码列表
Route Specification → List of location codes
节点ID列表→行程
List of Node IDs → Itinerary
为此,我们必须了解一个模型中某个元素的含义,并弄清楚如何用另一个模型来表达它。
To do this, we have to look at the meaning of an element of one model and figure out how to express it in terms of the other.
从第一个翻译(路线规范→位置代码列表)开始,我们需要考虑列表中位置顺序的含义。列表中的第一个位置是路径的起点,路径将依次经过每个位置,直到到达列表中的最后一个位置。因此,起点和终点分别是列表中的第一个和最后一个位置,而海关清关位置(如果有)则位于中间。
Starting with the first translation (Route Specification → List of location codes), we have to think about the meaning of the sequence of locations in the list. The first in the list will be the beginning of the path, which will then be forced to pass through each location in turn until it reaches the last location in the list. So the origin and destination are the first and last in the list, with the customs clearance location (if there is one) in the middle.
图 14.4. 将查询转换为网络遍历服务
Figure 14.4. Translation of a query to the Network Traversal Service
(值得庆幸的是,两支队伍使用了相同的地区代码,所以我们不必处理这方面的翻译工作。)
(Mercifully, the two teams used the same location codes, so we don’t have to deal with that level of translation.)
请注意,反向翻译会产生歧义,因为网络遍历输入允许任意数量的中间点,而不仅仅是一个专门指定的清关点。幸运的是,这不成问题,因为我们不需要反向翻译,但这让我们得以了解为什么有些翻译是不可能的。
Notice that the reverse translation would be ambiguous, because the network traversal input allows any number of intermediate points, not just one specifically designated as customs clearance point. Fortunately, this is no problem because we don’t need to translate in that direction, but it gives a glimpse of why some translations are impossible.
现在,让我们来转换结果(节点ID列表→行程)。我们假设可以使用存储库 (REPOSITORY)根据收到的节点ID 查找节点和运输操作对象。那么,这些节点如何映射到航段呢?根据这一点,我们可以将节点列表拆分为出发/到达对。每一对节点对应一个航段。operationType-Code
Now, let’s translate the result (List of Node IDs → Itinerary). We’ll assume that we can use a REPOSITORY to look up the Node and Shipping Operation objects based on the Node IDs we receive. So, how do those Nodes map to Legs? Based on the operationType-Code, we can break the list of Nodes into departure/arrival pairs. Each pair then relates to one Leg.
图 14.5.网络遍历服务找到的路径的转换
Figure 14.5. Translation of a route found by the Network Traversal Service
每个节点对的属性映射如下:
The attributes for each Node pair would be mapped as follows:
departureNode.shippingOperation.vesselVoyageId → leg.vesselVoyageId
departureNode.shippingOperation.date → leg.loadDate
departureNode.locationCode → leg.loadLocationCode
arrivalNode.shippingOperation.date → leg.unloadDate
arrivalNode.locationCode → leg.unloadLocationCode
这是这两个模型之间的概念转换图。现在我们需要实现一些能够帮我们完成转换的功能。在像这样的简单情况下,我通常会创建一个对象来实现这个功能,然后再找到或创建另一个对象来为子系统的其他部分提供服务。
This is the conceptual translation map between these two models. Now we have to implement something that can do the translation for us. In a simple case like this, I typically create an object for the purpose, and then find or create another object to provide the service to the rest of our subsystem.
图 14.6. 双向翻译器
Figure 14.6. A two-way translator
这是两个团队需要共同维护的唯一对象。设计应该便于单元测试,因此团队最好合作开发一套测试套件。除此之外,他们可以各自负责其他工作。
This is the one object that both teams have to work together to maintain. The design should make it very easy to unit-test, and it would be a particularly good idea for the teams to collaborate on a test suite for it. Other than that, they can go their separate ways.
图 14.7
Figure 14.7
路由服务的实现现在变成了委托给转换器和网络遍历服务。它的单个操作大致如下:
The Routing Service implementation now becomes a matter of delegating to the Translator and the Network Traversal Service. Its single operation would look something like this:
public Itinerary route(RouteSpecification spec) {
Booking_TransportNetwork_Translator translator =
new Booking_TransportNetwork_Translator();
List constraintLocations =
translator.convertConstraints(spec);
// Get access to the NetworkTraversalService
List pathNodes =
traversalService.findPath(constraintLocations);
Itinerary result = translator.convert(pathNodes);
return result;
}
还不错。“有界上下文”策略使每个模型都相对清晰,让各团队能够基本独立地开展工作,而且如果最初的假设是正确的,那么这些策略应该会很有效。(我们将在本章后面部分再讨论这一点。)
Not bad. The BOUNDED CONTEXTS served to keep each of the models relatively clean, let the teams work largely independently, and if initial assumptions had been correct, would probably have served well. (We’ll return to that later in this chapter.)
这两个上下文之间的接口相当小。路由服务的接口将预订上下文的其他部分与路线查找过程中发生的事件隔离开来。该接口易于测试,因为它由无副作用的函数组成。与其他上下文和谐共存的关键之一是为接口编写有效的测试集。“信任,但要核实,”里根总统在谈判裁军时曾这样说过。¹
The interface between the two contexts is fairly small. The interface of the Routing Service insulates the rest of the Booking CONTEXT’s design from events in the route-finding world. The interface is easy to test because it is made up of SIDE-EFFECT-FREE FUNCTIONS. One of the secrets to comfortable coexistence with other CONTEXTS is to have effective sets of tests for the interfaces. “Trust, but verify,” said President Reagan when negotiating arms reductions.1
设计一套自动化测试应该很容易,这些测试会将路线规范输入到路线服务中,并检查返回的行程。
It should be easy to devise a set of automated tests that would feed Route Specifications into the Routing Service and check the returned Itinerary.
模型上下文始终存在,但如果不加以有意识地关注,它们可能会重叠和波动。通过明确定义“有界上下文”和“上下文映射”,您的团队可以开始引导模型统一和不同模型连接的过程。
Model contexts always exist, but without conscious attention they may overlap and fluctuate. By explicitly defining BOUNDED CONTEXTS and a CONTEXT MAP, your team can begin to direct the process of unifying models and connecting distinct ones.
与其他限定上下文的接触点测试尤为重要。测试有助于弥补翻译中的细微差别以及边界处通常存在的低层次沟通。它们可以作为宝贵的预警系统,尤其是在您依赖于无法控制的模型细节时,更能起到安心作用。
Contact points with other BOUNDED CONTEXTS are particularly important to test. Tests help compensate for the subtleties of translation and the lower level of communication that typically exist at boundaries. They can act as a valuable early warning system, especially reassuring in cases where you depend on the details of a model you don’t control.
这里只有两点重要:
There are only two important points here:
1.限定上下文应该有名称,以便你们可以讨论它们。这些名称应该纳入团队的通用语言中。
1. The BOUNDED CONTEXTS should have names so that you can talk about them. Those names should enter the UBIQUITOUS LANGUAGE of the team.
2.每个人都必须知道界限在哪里,并且能够识别任何代码片段或任何情况的上下文。
2. Everyone has to know where the boundaries lie, and be able to recognize the CONTEXT of any piece of code or any situation.
第二个要求可以通过多种方式满足,具体取决于团队文化。一旦定义了限界上下文,自然而然地就会将不同上下文的代码分离到不同的模块中,这就引出了一个问题:如何跟踪哪个模块属于哪个上下文?可以使用命名约定来指示这一点,或者使用任何其他简单易行且避免混淆的机制。
The second requirement could be met in many ways depending on the culture of the team. Once the BOUNDED CONTEXTS have been defined, it comes naturally to segregate the code of different CONTEXTS into different MODULES, which leaves the question of how to keep track of which MODULE belongs in which CONTEXT. A naming convention might be used to indicate this, or any other mechanism that is easy and avoids confusion.
同样重要的是,要确保团队中的每个人都能以相同的方式理解概念边界。为了达到沟通的目的,我喜欢使用类似示例中的非正式图表。当然,也可以制作更严谨的图表或文本列表,展示每个上下文中的所有包,以及连接点和负责连接与转换的机制。有些团队会更适应这种方法,而另一些团队则可以通过口头协议和大量讨论来达成共识。
Equally important is communicating the conceptual boundaries in such a way that everyone on the team understands them the same way. For this communication purpose, I like informal diagrams like the ones in the example. More rigorous diagrams or textual lists could be made, showing all packages in each CONTEXT, along with the points of contact and the mechanisms responsible for connecting and translating. Some teams will be more comfortable with this approach, while others will get by fine based on spoken agreement and lots of discussion.
总之,如果要让名称进入通用语言,就必须将上下文图融入讨论中。不要说:“乔治团队的东西在变化,所以我们也得改变与之交互的东西。” 而应该说:“交通网络模型在变化,所以我们得改变预订上下文的翻译器。”
In any case, working the CONTEXT MAP into discussions is essential if the names are to enter the UBIQUITOUS LANGUAGE. Don’t say, “George’s team’s stuff is changing, so we’re going to have to change our stuff that talks to it.” Say instead, “The Transport Network model is changing, so we’re going to have to change the translator for the Booking context.”
以下模式涵盖了将两个模型关联起来的多种策略,这些模型可以组合起来以涵盖整个企业。这些模式具有双重目的:既为成功组织开发工作提供目标,又为描述现有组织提供词汇。
The following patterns cover a range of strategies for relating two models that can be composed to encompass an entire enterprise. These patterns serve the dual purpose of providing targets for successfully organizing development work, and supplying vocabulary for describing the existing organization.
一段现有的关系,无论是偶然还是有意为之,都可能接近这些模式中的某种,在这种情况下,你可以用该术语来描述它,并适当记录各种差异。然后,通过每一次细微的设计调整,这段关系都可以更接近所选的模式。
An existing relationship may, by chance or by design, fall near one of these patterns, in which case you can describe it using that term, variations duly noted. Then, with each small design change, the relationship can be drawn closer to the chosen pattern.
另一方面,您可能会发现现有的关系混乱或过于复杂。为了构建清晰明确的上下文图,可能需要进行一些重组。在这种情况下,或者在任何您考虑重组的情况下,这些模式呈现了一系列在不同情况下都适用的选择。主要变量包括你对其他模型的控制程度、团队间的合作水平和类型,以及功能和数据的整合程度。
On the other hand, you may find that an existing relationship is muddled or overcomplicated. Some reorganization might be necessary just to make an unambiguous CONTEXT MAP possible. In this situation, or any situation in which you are considering reorganization, these patterns present a range of choices that are favored in different circumstances. Prominent variables include the level of control you have over the other model, the level and type of cooperation between teams, and the degree of integration of features and data.
以下模式涵盖了一些最常见和最重要的案例,可以帮助您更好地理解如何处理其他案例。一个精干的团队紧密合作开发高度集成的产品,可以部署一个大型的统一模型。服务不同用户群体的需求或团队协调能力的限制可能会导致共享内核或客户/供应商关系。有时,仔细审视需求后会发现集成并非必要,系统各自独立运行才是最佳选择。当然,大多数项目都必须在一定程度上与遗留系统和外部系统集成,这可能会导致开放主机服务或防腐层。
The following set of patterns covers some of the most common and important cases, which should give you a good idea of how to approach other cases. A crack team working closely on a tightly integrated product can deploy a large unified model. The need to serve different user communities or a limitation on the coordination abilities of the team might lead to a SHARED KERNEL or CUSTOMER/SUPPLIER relationships. Sometimes a good hard look at the requirements reveals that integration is not essential and it is best for systems to go their SEPARATE WAYS. And, of course, most projects have to integrate to some degree with legacy and external systems, which can lead to OPEN HOST SERVICES or ANTICORRUPTION LAYERS.
当功能集成受限时,持续集成的开销可能过高。尤其当团队缺乏维持持续集成所需的技能或组织架构,或者单个团队规模过大、难以管理时,更是如此。因此,可以定义独立的限界环境,并组建多个团队。
When functional integration is limited, the overhead of CONTINUOUS INTEGRATION may be deemed too high. This may especially be true when the teams do not have the skill or the political organization to maintain continuous integration, or when a single team is simply too big and unwieldy. So separate BOUNDED CONTEXTS might be defined and multiple teams formed.
各自独立开发密切相关应用程序的团队,虽然可能暂时高速推进,但最终成果可能无法兼容。他们最终可能在转换层和后期改造上花费比一开始就采用持续集成方案更高的成本,同时还会造成工作重复,并失去通用语言带来的优势。
Uncoordinated teams working on closely related applications can go racing forward for a while, but what they produce may not fit together. They can end up spending more on translation layers and retrofitting than they would have on CONTINUOUS INTEGRATION in the first place, meanwhile duplicating effort and losing the benefits of a common UBIQUITOUS LANGUAGE.
在许多项目中,我看到基础设施层是由几个基本独立工作的团队共享的。这种模式同样适用于本领域。虽然完全同步整个模型和代码库可能开销过大,但精心挑选一个子集就能以更低的成本带来诸多益处。
On many projects I’ve seen the infrastructure layer shared among teams that worked largely independently. An analogy to this can work well within the domain as well. It may be too much overhead to fully synchronize the entire model and code base, but a carefully selected subset can provide much of the benefit for less cost.
所以:
Therefore:
指定两个团队同意共享的领域模型的某个子集。当然,这包括,除了这个子集之外,模型,以及与模型该部分相关的代码或数据库设计子集。这些明确共享的内容具有特殊地位,未经其他团队协商不得更改。
Designate some subset of the domain model that the two teams agree to share. Of course this includes, along with this subset of the model, the subset of code or of the database design associated with that part of the model. This explicitly shared stuff has special status, and shouldn’t be changed without consultation with the other team.
频繁地集成功能系统,但频率要略低于团队内部持续集成(CIT)的频率。在这些集成过程中,两个团队都要进行测试。
Integrate a functional system frequently, but somewhat less often than the pace of CONTINUOUS INTEGRATION within the teams. At these integrations, run the tests of both teams.
这需要谨慎权衡。共享内核的修改不能像设计的其他部分那样自由。所有决策都需要与其他团队协商。自动化测试套件必须集成,因为每次修改后,两个团队的所有测试都必须通过。通常,团队会在内核的独立副本上进行修改,并定期与其他团队进行集成。(例如,对于一个每天甚至更频繁地进行持续集成的团队,内核合并可能每周一次。)无论代码集成何时安排,两个团队越早讨论这些变更越好。
It is a careful balance. The SHARED KERNEL cannot be changed as freely as other parts of the design. Decisions involve consultation with another team. Automated test suites must be integrated because all tests of both teams must pass when changes are made. Usually, teams make changes on separate copies of the KERNEL, integrating with the other team at intervals. (For example, on a team that CONTINUOUSLY INTEGRATES daily or better, the KERNEL merger might be weekly.) Regardless of when code integration is scheduled, the sooner both teams talk about the changes, the better.
共享内核通常是核心域、一组通用子域,或两者兼有(参见第15章),但也可以是两个团队所需的模型中的任何部分。其目标是减少重复(但并非消除重复,如果只有一个限界上下文,则可以完全消除重复),并使两个子系统之间的集成相对容易。
The SHARED KERNEL is often the CORE DOMAIN, some set of GENERIC SUBDOMAINS, or both (see Chapter 15), but it can be any part of the model that is needed by both teams. The goal is to reduce duplication (but not to eliminate it, as would be the case if there were just one BOUNDED CONTEXT) and make integration between the two subsystems relatively easy.
通常情况下,一个子系统本质上是另一个子系统的“源泉”;“下游”组件执行分析或其他功能,但对“上游”组件的反馈却很少,所有依赖关系都是单向的。这两个子系统通常服务于截然不同的用户群体,他们从事不同的工作,因此可能需要不同的模型。工具集也可能不同,所以程序代码无法共享。
Often one subsystem essentially feeds another; the “downstream” component performs analysis or other functions that feed back very little into the “upstream” component, and all dependencies go one way. The two subsystems commonly serve very different user communities, who do different jobs, where different models may be useful. The tool set may also be different, so that program code cannot be shared.
上游和下游子系统自然而然地分离成两个独立的上下文。当这两个组件需要不同的技能或使用不同的工具集进行实施时,这一点尤为明显。由于只需单向操作,翻译工作相对容易。但问题也可能随之出现,这取决于两个团队之间的政治关系。
Upstream and downstream subsystems separate naturally into two BOUNDED CONTEXTS. This is especially true when the two components require different skills or employ a different tool set for implementation. Translation is easier for having to operate in one direction only. But problems can emerge, depending on the political relationship of the two teams.
如果下游团队对变更拥有否决权,或者变更申请流程过于繁琐,上游团队的自由开发就会受到限制。上游团队甚至可能因为担心破坏下游系统而受到束缚。与此同时,下游团队则可能束手无策,只能任由上游团队的优先级摆布。
The freewheeling development of the upstream team can be cramped if the downstream team has veto power over changes, or if procedures for requesting changes are too cumbersome. The upstream team may even be inhibited, worried about breaking the downstream system. Meanwhile, the downstream team can be helpless, at the mercy of upstream priorities.
下游需要上游提供的资源,但上游并不负责向下游交付成果。这需要付出很多额外的努力。要预判哪些因素会影响对方团队,考虑到人性的复杂性和时间压力,嗯……将团队间的关系正式化,会让每个人都更轻松。流程可以进行合理安排,以平衡两个用户群体的需求,并按计划推进后续所需功能的开发。
Downstream needs things from upstream, but upstream is not responsible for downstream deliverables. It takes a lot of extra effort to anticipate what will affect the other team, and human nature being what it is, and time pressures being what they are, well . . . . It makes everyone’s life easier to formalize the relationship between the teams. The process can be organized to balance the needs of the two user communities and schedule work on features needed downstream.
在极限编程项目中,已经存在一种机制可以实现这一点:迭代计划流程。我们只需根据计划流程定义两个团队之间的关系即可。下游团队的代表可以像用户代表一样发挥作用,参与计划会议,直接与他们的“客户”讨论他们想要完成的任务的权衡取舍。最终,供应商团队会得到一份迭代计划,其中包含下游团队最需要的任务,或者通过协商推迟某些任务,从而避免对交付时间产生预期。
On an Extreme Programming project, there already is a mechanism in place for doing just that: the iteration planning process. All we have to do is define the relationship between the two teams in terms of the planning process. Representatives of the downstream team can function much like the user representatives, joining them in planning sessions, discussing directly with their fellow “customers” the trade-offs for the tasks they want. The result is an iteration plan for the supplier team that includes tasks the downstream team needs most or defers tasks by agreement, so there is no expectation of delivery.
如果采用 XP 以外的流程,任何能够平衡不同用户需求的类似方法都可以扩展到包括下游应用程序的需求。
If a process other than XP is used, whatever analogous method serves to balance the concerns of different users can be expanded to include the downstream application’s needs.
所以:
Therefore:
在两个团队之间建立清晰的客户/供应商关系。在计划会议中,让下游团队扮演客户的角色,向上游团队提出建议。协商并制定下游需求的预算,确保每个人都了解各自的承诺和进度安排。
Establish a clear customer/supplier relationship between the two teams. In planning sessions, make the downstream team play the customer role to the upstream team. Negotiate and budget tasks for downstream requirements so that everyone understands the commitment and schedule.
共同开发自动化验收测试,以验证预期接口。将这些测试添加到上游团队的测试套件中,作为其持续集成流程的一部分运行。此测试将使上游团队能够放心地进行更改,而无需担心对下游产生副作用。
Jointly develop automated acceptance tests that will validate the interface expected. Add these tests to the upstream team’s test suite, to be run as part of its continuous integration. This testing will free the upstream team to make changes without fear of side effects downstream.
在迭代过程中,下游团队成员需要像普通客户一样随时为上游开发人员提供帮助,解答疑问并协助解决问题。
During the iteration, the downstream team members need to be available to the upstream developers just as conventional customers are, to answer questions and help resolve problems.
自动化验收测试是客户关系中至关重要的一环。即使在合作最密切的项目中,尽管客户能够识别并沟通其依赖关系,供应商也能尽力沟通变更,但如果没有测试,意外情况仍然会发生。这些意外会扰乱下游团队的工作,迫使上游团队承担计划外的紧急修复工作。因此,应该让客户团队与供应商团队协作,开发自动化验收测试,以验证其预期的接口。上游团队会将这些测试作为其标准测试套件的一部分运行。任何对这些测试的更改都需要与上游团队沟通,因为更改测试意味着更改接口。
Automating the acceptance tests is a vital part of this customer relationship. Even on the most cooperative project, although the customer can identify and communicate its dependencies, and the supplier can diligently try to communicate changes, without tests, surprises will happen. They will disrupt the downstream team’s work and force the upstream team to take on unscheduled, emergency fixes. Instead, have the customer team, in collaboration with the supplier team, develop automated acceptance tests that will validate the interface it expects. The upstream team will run these tests as part of its standard test suite. Any change to these tests calls for communication with the other team, because changing the tests implies changing the interface.
客户/供应商关系也会出现在不同公司的项目之间,尤其是在某个客户对供应商业务至关重要的情况下。有时,这种关系会反过来影响上游项目:有影响力的客户可能会提出对上游项目成功至关重要的要求,但这些要求也可能对上游项目的开发造成干扰。双方都能从需求响应流程的规范化中获益,因为在外部关系中,成本效益的权衡比在内部IT环境中更难把握。
Customer/supplier relationships also emerge between projects in separate companies, in situations where a single customer is very important to the business of the supplier. The tail can wag the dog: an influential customer can make demands that are important to the upstream project’s success, but those demands can also be disruptive to the upstream project’s development. Both parties benefit from the formalization of the process of responding to requirements, because the cost/benefit trade-offs are even harder to see in external relationships than they are in the internal IT situation.
这种模式包含两个关键要素。
There are two crucial elements to this pattern.
1.这种关系必须是客户与供应商的关系,这意味着客户的需求至关重要。由于下游团队并非唯一的客户,因此在谈判中必须平衡不同客户的需求——但客户的需求始终是优先事项。这种情况与经常出现的“穷亲戚”关系截然不同,在那种关系中,下游团队不得不乞求上游团队满足其需求。
1. The relationship must be that of customer and supplier, with the implication that the customer’s needs are paramount. Because the downstream team is not the only customer, the different customers’ demands have to be balanced in negotiation—but they remain priorities. This situation is in contrast to the poor-cousin relationship that often emerges, in which the downstream team has to come begging to the upstream team for its needs.
2.必须有一个自动化测试套件,使上游团队能够更改其代码而不用担心破坏下游,并使下游团队能够专注于自己的工作而无需不断监控上游团队。
2. There must be an automated test suite that allows the upstream team to change its code without fear of breaking the downstream, and lets the downstream team concentrate on its own work without constantly monitoring the upstream team.
在接力赛中,前棒选手不能一直回头查看。他/她必须信任接力棒的传递者能够精准地完成交接,否则队伍的速度将会被严重拖慢。
In a relay race, the forward runner can’t be looking backward all the time, checking. He or she has to be able to trust the baton carrier to make the handoff precisely, or else the team will be hopelessly slowed down.
回到我们熟悉的航运例子。公司成立了一个高度专业化的团队,负责分析所有通过公司进行的预订,以找出最大化收入的方法。团队成员可能会发现船上有空位,并建议增加超额预订。可能会发现船舶很早就被散货填满,迫使公司拒绝利润更高的特种货物运输。在这种情况下,他们可能会建议为这类货物预留舱位,或者提高散货运输的价格。
Back to our trusty shipping example. A highly specialized team has been set up to analyze all the bookings that flow through the firm, to see how to maximize income. Team members might find that ships have empty space and might recommend more overbooking. They might find that the ships are filling up with bulk freight early, forcing the company to turn away more lucrative specialty cargoes. In that case they might recommend reserving space for these types of cargo or raising prices on the bulk freight.
为了进行这项分析,他们使用了自己开发的复杂模型。在实施过程中,他们使用了一个包含分析模型构建工具的数据仓库。此外,他们还需要从Booking应用程序中获取大量信息。
To do this analysis, they use their own complex models. For implementation, they use a data warehouse with tools for building analytical models. And they need lots of information from the Booking application.
从一开始就很明显,这是两个有界上下文,因为它们使用了不同的实现工具,更重要的是,使用了不同的领域模型。它们之间应该是什么关系?
From the start, it is clear that these are two BOUNDED CONTEXTS, because they use different implementation tools and, most important, different domain models. What should the relationship between them be?
共享内核看似合乎逻辑,因为收益分析关注的是 Booking 模型的一个子集,而他们自己的模型在货物、价格等方面与 Booking 模型存在一些重叠的概念。但是,如果采用不同的实现技术,共享内核就难以实现。此外,收益分析团队的建模需求非常特殊,他们会不断地调整模型并尝试不同的方案。他们或许应该将 Booking上下文中所需的内容转换成自己的模型。(另一方面,如果他们可以使用共享内核,转换工作量将大大减轻。他们仍然需要重新实现模型并将数据转换到新的实现中,但如果模型相同,转换过程应该很简单。)
A SHARED KERNEL might seem logical, because yield analysis is interested in a subset of the Booking’s model, and their own model has some overlapping concepts of cargos, prices, and so on. But SHARED KERNEL is difficult in a case where different implementation technologies are being used. Besides, the modeling needs of the yield analysis team are quite specialized, and they continuously play with their models and try alternative ones. They may well be better off translating what they need from the Booking CONTEXT into their own. (On the other hand, if they can use a SHARED KERNEL, their translation burden will be much lighter. They will still have to reimplement the model and translate the data to the new implementation, but if the model is the same, the transfer should be simple.)
Booking应用程序不依赖于收益分析,因为我们没有自动调整策略的计划。决策将由专业人员做出,并传达给相关人员和系统。因此,我们之间存在上下游关系。下游需求如下:
The Booking application has no dependency on the yield analysis, because there is no intention of automatically adjusting policies. Human specialists will make the decisions and convey them to the needed people and systems. So we have an upstream/downstream relationship. What downstream needs is this:
1.部分数据并非任何预订操作所必需。
1. Some data not needed by any booking operation
2.数据库模式有一定的稳定性(或者至少能可靠地通知用户变更),或者需要一个导出工具。
2. Some stability in database schema (or at least reliable notification of change) or an export utility
幸运的是,Booking应用程序开发团队的项目经理积极主动地帮助收益分析团队。这原本可能是一个问题,因为实际负责日常预订的运营部门向另一位副总裁汇报工作,而另一位副总裁则向Booking应用程序开发团队的项目经理汇报工作。实际进行收益分析的人员。但高层管理人员非常重视收益管理,并且鉴于过去两个部门之间合作存在问题,他们调整了软件开发项目的结构,使两个团队的项目经理都向同一个人汇报。
Fortunately, the project manager of the Booking application development team is motivated to help the yield analysis team. This could have been a problem, because the operations department that actually does day-to-day booking reports to a different vice president than the people who actually do yield analysis. But the upper management cares deeply about yield management and, having seen past cooperation problems between the two departments, structured the software development project so that the project managers of both teams report to the same person.
因此,所有条件都已具备,可以组建客户/供应商开发团队。
Therefore, all the requirements are in place to apply CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
我曾在多个地方见过这种情景:分析软件开发人员和运维软件开发人员之间存在着客户/供应商关系。当上游团队成员将自己的角色定位为服务客户时,事情进展得相当顺利。这种合作几乎总是非正式的,而且每次的效果都和两位项目经理的私人关系差不多。
I’ve seen this scenario evolve in multiple places, where analysis software developers and operations software developers had a customer/supplier relationship. When the upstream team members thought of their role as serving a customer, things worked out pretty well. It was almost always organized informally, and in each case it worked out about as well as the personal relationship of the two project managers.
在一个极限编程(XP)项目中,我看到这种关系被正式化:在每次迭代中,下游团队的代表都会扮演客户的角色,参与“计划制定”游戏,与负责应用功能的传统客户代表一起协商哪些任务最终纳入迭代计划。这个项目是在一家小公司进行的,所以离他们最近的共同领导层级并不高。这种模式运作得非常成功。
On one XP project, I saw this relationship formalized in the sense that, for each iteration, representatives of the downstream team played the “planning game” in the role of customers, huddling with the more conventional customer representatives (of application functionality) to negotiate which tasks made it into the iteration plan. This project was at a small company, and so the nearest shared boss was not far up the chain. It worked very well.
客户/供应商团队如果在同一管理团队的领导下工作,最终拥有共同目标,或者他们分别隶属于不同的公司且各自承担相应的职责,那么这两个团队更容易取得成功。但如果上游团队缺乏激励,情况则截然不同……
CUSTOMER/SUPPLIER TEAMS are more likely to succeed if the two teams work under the same management, so that ultimately they do share goals, or where they are in different companies that actually have those roles. When there is nothing to motivate the upstream team, the situation is very different. . . .
当两个具有上下游关系的团队没有得到同一领导层的有效指导时,诸如“客户/供应商团队”之类的合作模式就行不通了。贸然尝试只会让下游团队陷入困境。这种情况常见于大型公司,例如两个团队在管理层级上相距甚远,或者共同的领导对两个团队的关系漠不关心。此外,当客户的业务对供应商而言并非至关重要时,不同公司的团队之间也会出现这种情况。或许供应商拥有众多小型客户,或许供应商正在改变市场方向,不再重视老客户。供应商可能管理不善,甚至可能已经倒闭。无论原因如何,现实情况是下游团队只能靠自己。
When two teams with an upstream/downstream relationship are not effectively being directed from the same source, a cooperative pattern such as CUSTOMER/SUPPLIER TEAMS is not going to work. Naively trying to apply it will get the downstream team into trouble. This can be the case in a large company in which the two teams are far apart in the management hierarchy or where the shared supervisor is indifferent to the relationship of the two teams. It also arises between teams in different companies when the customer’s business is not individually important to the supplier. Perhaps the supplier has many small customers, or perhaps the supplier is changing market direction and no longer values the old customers. The supplier may just be poorly run. It may have gone out of business. Whatever the reason, the reality is that the downstream is on its own.
当两个开发团队之间存在上下游关系,而上游团队没有动力满足下游团队的需求时,下游团队就束手无策。利他主义或许会促使上游开发者做出承诺,但这些承诺不太可能兑现。对这些良好意愿的盲目信任只会导致……下游团队需要根据永远不会实现的功能来制定计划。下游项目将会延期,直到团队最终学会接受现状。为下游团队量身定制的界面不在考虑范围内。
When two development teams have an upstream/downstream relationship in which the upstream has no motivation to provide for the downstream team’s needs, the downstream team is helpless. Altruism may motivate upstream developers to make promises, but they are unlikely to be fulfilled. Belief in those good intentions leads the downstream team to make plans based on features that will never be available. The downstream project will be delayed until the team ultimately learns to live with what it is given. An interface tailored to the needs of the downstream team is not in the cards.
在这种情况下,有三种可能的解决方案。一是完全放弃使用上游。这个方案需要进行务实的评估,不能假设上游会满足下游的需求。有时我们会高估这种依赖的价值或低估其成本。如果下游团队决定切断与上游的联系,那么他们将分道扬镳(详见本章后面的模式描述)。
In this situation, there are three possible paths. One is to abandon use of the upstream altogether. This option should be evaluated realistically, making no assumptions that the upstream will accommodate downstream needs. Sometimes we overestimate the value or underestimate the cost of such a dependency. If the downstream team decides to cut the strings, they are going their SEPARATE WAYS (see the pattern description later in this chapter).
有时,使用上游软件的价值如此之大,以至于必须维持这种依赖关系(或者团队已经做出了无法更改的政治决定)。在这种情况下,还有两条路可走;选择取决于上游设计的质量和风格。如果设计非常难以使用,可能是由于缺乏封装、抽象生硬,或者采用了团队无法使用的建模范式,那么下游团队仍然需要开发自己的模型。他们将不得不承担起构建一个可能非常复杂的转换层的全部责任。(参见本章后面的“反腐败层”部分。)
Sometimes the value of using the upstream software is so great that the dependency has to be maintained (or a political decision has been made that the team cannot change). In this case, two paths remain open; the choice depends on the quality and style of the upstream design. If the design is very difficult to work with, perhaps for lack of encapsulation, awkward abstractions, or modeling in a paradigm the team cannot use, then the downstream team will still need to develop its own model. They will have to take full responsibility for a translation layer that is likely to be complex. (See ANTICORRUPTION LAYER, later in this chapter.).
另一方面,如果质量还不错,风格也比较协调,那么最好完全放弃独立模式。这种情况就需要一位循规蹈矩的人。
On the other hand, if the quality is not so bad, and the style is reasonably compatible, then it may be best to give up on an independent model altogether. This is the circumstance that calls for a CONFORMIST.
所以:
Therefore:
通过严格遵循上游团队的模型,消除限界上下文之间翻译的复杂性。虽然这会限制下游设计师的风格,并且可能无法产生最适合应用程序的模型,但选择一致性可以极大地简化集成。此外,您还将与供应商团队共享一种通用语言。供应商掌握着主动权,因此最好让他们的沟通变得轻松便捷。或许,出于利他主义,他们就愿意与您分享信息。
Eliminate the complexity of translation between BOUNDED CONTEXTS by slavishly adhering to the model of the upstream team. Although this cramps the style of the downstream designers and probably does not yield the ideal model for the application, choosing CONFORMITY enormously simplifies integration. Also, you will share a UBIQUITOUS LANGUAGE with your supplier team. The supplier is in the driver’s seat, so it is good to make communication easy for them. Altruism may be sufficient to get them to share information with you.
这个决定加深了你对上游的依赖,并将你的应用程序限制在上游模型的功能范围内——外加一些纯粹的附加增强功能。这在情感上非常令人反感,这就是为什么我们选择它的频率可能远低于我们应该选择的程度。
This decision deepens your dependency on the upstream and limits your application to the capabilities of the upstream model—plus purely additive enhancements. It is very unappealing emotionally, which is why we choose it less often than we probably should.
如果这些权衡是不可接受的,但上游依赖又不可或缺,那么还有第二个选择:通过创建一个防腐层来尽可能地隔离自己,这是一种激进的翻译映射实现方法,稍后会讨论。
If these trade-offs are not acceptable, but the upstream dependency is indispensable, the second option still remains: Insulate yourself as much as possible by creating an ANTICORRUPTION LAYER, an aggressive approach to implementing a translation map that will be discussed later.
CONFORMIST与SHARED KERNEL 的相似之处在于,两者都存在重叠区域:模型相同的区域、模型通过添加扩展的区域,以及对方模型不影响的区域。两种模式的区别在于决策和开发流程。SHARED KERNEL是两个紧密协作的团队之间的合作,而 CONFORMIST 则涉及与一个不热衷于合作的团队进行集成。
CONFORMIST resembles SHARED KERNEL in that both have an overlapping area where the model is the same, areas where your model has been extended by addition, and areas where the other model does not affect you. The difference between the patterns is in the decision-making and development processes. Where the SHARED KERNEL is a collaboration between two teams that coordinate tightly, CONFORMIST deals with integration with a team that is not interested in collaboration.
我们一直在探讨有限上下文整合中的合作程度,从高度合作的共享内核或客户/供应商开发团队,到单方面的顺从者模式。现在,我们将迈向最后一步,对这种关系持更加悲观的看法,假设对方既不合作,也没有可用的设计……
We’ve been proceeding down a spectrum of cooperation in the integration between BOUNDED CONTEXTS, from highly cooperative SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS to the one-sidedness of the CONFORMIST. Now we’ll take the final step to an even more pessimistic view of the relationship, assuming neither cooperation nor a usable design on the other side. . . .
新系统几乎总是需要与拥有各自模型的遗留系统或其他系统集成。当连接设计良好的限界上下文和协作团队时,转换层可以很简单,甚至很优雅。但当边界另一侧的信息开始泄露时,转换层可能会变得更加防御性。
New systems almost always have to be integrated with legacy or other systems, which have their own models. Translation layers can be simple, even elegant, when bridging well-designed BOUNDED CONTEXTS with cooperative teams. But when the other side of the boundary starts to leak through, the translation layer may take on a more defensive tone.
当构建一个必须与另一个系统进行大量接口的新系统时,关联两个模型的难度最终可能会完全掩盖新模型的初衷,导致新模型不得不以一种临时的方式进行修改,以使其与另一个系统的模型相似。遗留系统的模型通常比较薄弱,即使是少数发展完善的模型也可能无法满足当前项目的需求。然而,集成可能具有巨大的价值,有时甚至是绝对必要的。
When a new system is being built that must have a large interface with another, the difficulty of relating the two models can eventually overwhelm the intent of the new model altogether, causing it to be modified to resemble the other system’s model, in an ad hoc fashion. The models of legacy systems are usually weak, and even the exception that is well developed may not fit the needs of the current project. Yet there may be a lot of value in the integration, and sometimes it is an absolute requirement.
答案并非完全避免与其他系统集成。我曾参与过一些项目,参与者热情地着手替换所有遗留系统,但这实在是难以一次性完成。此外,与现有系统集成也是一种宝贵的重用方式。在大型项目中,一个子系统通常需要与多个独立开发的子系统进行交互。这些子系统对问题的理解各不相同。当基于不同模型的系统组合在一起时,新系统为了适应其他系统的语义,可能会导致自身语义的破坏。模型。即使另一个系统设计得很好,它也不是基于与客户相同的模型。而且通常情况下,另一个系统的设计并不好。
The answer is not to avoid all integration with other systems. I’ve been on projects where people enthusiastically set out to replace all the legacy, but this is just too much to take on at once. Besides, integrating with existing systems is a valuable form of reuse. On a large project, one subsystem will often have to interface with several other, independently developed subsystems. These will reflect the problem domain differently. When systems based on different models are combined, the need for the new system to adapt to the semantics of the other system can lead to a corruption of the new system’s own model. Even when the other system is well designed, it is not based on the same model as the client. And often the other system is not well designed.
与外部系统交互会遇到诸多障碍。例如,基础设施层必须提供与可能运行在不同平台或使用不同协议的另一个系统通信的手段。另一个系统的数据类型必须转换为本系统的数据类型。但常常被忽略的是,另一个系统使用的领域概念模型可能与本系统不同。
There are many hurdles in interfacing with an external system. For example, the infrastructure layer must provide the means to communicate with another system that might be on a different platform or use different protocols. The data types of the other system must be translated into those of your system. But often overlooked is the certainty that the other system does not use the same conceptual domain model.
显而易见,如果从一个系统中提取数据并错误地将其导入另一个系统,就会出现错误,甚至可能损坏数据库。然而,这个问题往往会悄然出现,因为我们通常认为在系统间传输的是原始数据,其含义明确,在两个系统中必然相同。这种假设通常是错误的。数据在每个系统中的关联方式不同,会导致细微却重要的含义差异。即使原始数据元素的含义完全相同,让与另一个系统的接口运行在如此低的层级上通常也是一个错误。低层接口会削弱另一个系统模型解释数据、约束其值和关系的能力,同时让新系统承担解释自身模型无法解释的原始数据的重担。
It seems clear enough that errors will result if you take some data from one system and misinterpret it in another. You may even corrupt the database. But even so, this problem tends to sneak up on us because we think that what we are transporting between systems is primitive data, whose meaning is unambiguous and must be the same on both sides. This assumption is usually wrong. Subtle yet important differences in meaning arise from the way the data are associated in each system. And even if primitive data elements do have exactly the same meaning, it is usually a mistake to make the interface to the other system operate at such a low level. A low-level interface takes away the power of the other system’s model to explain the data and constrain its values and relationships, while saddling the new system with the burden of interpreting primitive data that is not in terms of its own model.
我们需要在遵循不同模型的各个部分之间进行转换,这样模型就不会被未经消化的外来模型元素所破坏。
We need to provide a translation between the parts that adhere to different models, so that the models are not corrupted with undigested elements of foreign models.
所以:
Therefore:
创建一个隔离层,以便根据客户端自身的领域模型为其提供功能。该层通过现有接口与其他系统通信,几乎无需对其他系统进行任何修改。在内部,该层会根据需要在两个模型之间进行双向转换。
Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.
讨论连接两个系统的机制可能会让人联想到数据在不同程序之间或服务器之间的传输问题。我稍后会讨论技术通信机制的整合。但这些细节不应该……不要将其与反腐败层混淆,反腐败层并非向其他系统发送消息的机制,而是一种将概念对象和动作从一种模型和协议转换为另一种模型和协议的机制。
This discussion of a mechanism to link two systems might bring to mind issues of transporting the data from one program to another or from one server to another. I’ll discuss the incorporation of the technical communications mechanism shortly. But such details shouldn’t be confused with an ANTICORRUPTION LAYER, which is not a mechanism for sending messages to another system. Rather, it is a mechanism that translates conceptual objects and actions from one model and protocol to another.
反腐败层本身可以成为一个非常复杂的软件。接下来,我将概述创建反腐败层时需要考虑的一些设计因素。
An ANTICORRUPTION LAYER can become a complex piece of software in its own right. Next I’ll outline some of the design considerations for creating one.
反腐败层的公共接口通常以服务(SERVICE)集合的形式出现,但有时也可能以实体(ENTITY)的形式出现。构建一个全新的层来负责两个系统语义之间的转换,使我们能够重新抽象出另一个系统的行为,并根据我们的模型,将其服务和信息提供给我们的系统。在我们的模型中,将外部系统表示为单个组件可能并不合理。最好使用多个服务(或偶尔使用实体),每个服务在我们的模型中都承担着明确的职责。
The public interface of the ANTICORRUPTION LAYER usually appears as a set of SERVICES, although occasionally it can take the form of an ENTITY. Building a whole new layer responsible for the translation between the semantics of the two systems gives us an opportunity to reabstract the other system’s behavior and offer its services and information to our system consistently with our model. It may not even make sense, in our model, to represent the external system as a single component. It may be best to use multiple SERVICES (or occasionally ENTITIES), each of which has a coherent responsibility in terms of our model.
反腐败层的设计组织方式之一是将外观、适配器(均来自Gamma 等人 1995 年的研究)和翻译器,以及系统之间通常需要的通信和传输机制结合起来。
One way of organizing the design of the ANTICORRUPTION LAYER is as a combination of FACADES, ADAPTERS (both from Gamma et al. 1995), and translators, along with the communication and transport mechanisms usually needed to talk between systems.
我们经常需要集成那些拥有庞大、复杂且混乱接口的系统。这属于实现层面的问题,而非出于概念模型差异而引入反腐败层的原因,但确实是在创建反腐败层时会遇到的问题。从一个模型转换到另一个模型(尤其是在一个模型模糊的情况下)本身就已十分困难,更何况还要同时处理难以交互的子系统接口。幸运的是,外观模式正是为此而生的。
We often have to integrate with systems that have large, complicated, messy interfaces. This is an implementation issue, not an issue of conceptual model differences that motivated the use of ANTICORRUPTION LAYERS, but it is a problem you’ll encounter trying to create them. Translating from one model to another (especially if one model is fuzzy) is a hard enough job without simultaneously dealing with a subsystem interface that is hard to talk to. Fortunately, that is what FACADES are for.
外观界面(FACADE)是子系统的替代接口,它简化了客户端的访问,并使子系统更易于使用。因为我们确切地知道要使用其他系统的哪些功能,所以我们可以创建一个外观界面,方便快捷地访问这些功能,并隐藏其余部分。外观界面并不 更改底层系统的模型。它必须严格按照另一个系统的模型编写。否则,最好的结果是将转换责任分散到多个对象,导致外观(FACADE )过载;最坏的结果则是创建另一个模型,该模型既不属于另一个系统,也不属于您自己的有界上下文。外观(FACADE)属于另一个系统的有界上下文。它只是提供了一个更友好的界面,专门用于满足您的需求。
A FACADE is an alternative interface for a subsystem that simplifies access for the client and makes the subsystem easier to use. Because we know exactly what functionality of the other system we want to use, we can create a FACADE that facilitates and streamlines access to those features and hides the rest. The FACADE does not change the model of the underlying system. It should be written strictly in accordance with the other system’s model. Otherwise, you will at best diffuse responsibility for translation into multiple objects and overload the FACADE and at worst end up creating yet another model, one that doesn’t belong to the other system or your own BOUNDED CONTEXT. The FACADE belongs in the BOUNDED CONTEXT of the other system. It just presents a friendlier face specialized for your needs.
ADAPTER是一种包装器,它允许客户端使用与行为实现者所理解的协议不同的协议。当客户端向ADAPTER发送消息时,该消息会被转换为语义等效的消息,并发送给“被适配对象”。响应也会被转换并返回。我在这里对“适配器”一词的使用比较宽泛,因为Gamma 等人 1995 年的论文重点在于使被包装的对象符合客户端期望的标准接口,而我们可以选择适配的接口,并且被适配对象可能甚至不是一个对象。我们关注的是两种模型之间的转换,但我认为这与ADAPTER的初衷是一致的。
An ADAPTER is a wrapper that allows a client to use a different protocol than that understood by the implementer of the behavior. When a client sends a message to an ADAPTER, it is converted to a semantically equivalent message and sent on to the “adaptee.” The response is converted and passed back. I’m using the term adapter a little loosely, because the emphasis in Gamma et al. 1995 is on making a wrapped object conform to a standard interface that clients expect, whereas we get to choose the adapted interface, and the adaptee is probably not even an object. Our emphasis is on translation between two models, but I think this is consistent with the intent of ADAPTER.
对于我们定义的每个服务,我们需要一个适配器来支持该服务的接口,并且知道如何向另一个系统或其外观发出等效请求。
For each SERVICE we define, we need an ADAPTER that supports the SERVICE’S interface and knows how to make equivalent requests of the other system or its FACADE.
剩下的部分是转换器。适配器的任务是知道如何发出请求。概念对象或数据的实际转换是一个独立的复杂任务,可以将其放在一个单独的对象中,这样两者都更容易理解。转换器可以是一个轻量级对象,在需要时实例化。它不需要状态,也不需要分布式部署,因为它属于它所服务的适配器。
The remaining element is the translator. The ADAPTER’S job is to know how to make a request. The actual conversion of conceptual objects or data is a distinct, complex task that can be placed in its own object, making them both much easier to understand. A translator can be a lightweight object that is instantiated when needed. It needs no state and does not need to be distributed, because it belongs with the ADAPTER(S) it serves.
以上是我用来构建反腐败层的基本要素。此外,还有一些其他需要考虑的因素。
Those are the basic elements I use to create an ANTICORRUPTION LAYER. There are a few other considerations.
通常情况下,如图 14.8所示,正在设计的系统(即您的子系统)会主动发起操作。然而,有时另一个子系统可能需要向您的子系统请求某些内容或通知您某些事件。防腐层可以是双向的,在两个接口上定义服务,并各自配备适配器,还可以使用相同的转换器进行对称转换。虽然实现防腐层通常不需要对另一个子系统进行任何更改,但为了让另一个系统调用防腐层的服务,有时可能需要进行一些更改。
• Typically, the system under design (your subsystem) will be initiating action, as implied by Figure 14.8. There are cases, however, when the other subsystem may need to request something of your subsystem or notify it of some event. An ANTICORRUPTION LAYER can be bidirectional, defining SERVICES on both interfaces with their own ADAPTERS, potentially using the same translators with symmetrical translations. Although implementing the ANTICORRUPTION LAYER doesn’t usually require any change to the other subsystem, it might be necessary in order to make the other system call on SERVICES of the ANTICORRUPTION LAYER.
图 14.8反腐败层的结构
Figure 14.8. The structure of an ANTICORRUPTION LAYER
通常情况下,您需要某种通信机制来连接两个子系统,它们很可能位于不同的服务器上。在这种情况下,您必须决定将这些通信链路放置在何处。如果您无法访问另一个子系统,则可能需要将链路放置在FACADE和另一个子系统之间。但是,如果FACADE可以直接与另一个子系统集成,那么一个不错的选择是将通信链路放置在ADAPTER和FACADE之间,因为FACADE的协议通常比它所涵盖的协议更简单。此外,在某些情况下,整个防腐层可以与另一个子系统共存,只需在您的子系统和构成防腐层接口的服务之间放置通信链路或分发机制即可。这些都是需要根据实际情况做出的实现和部署决策,与防腐层的概念作用无关。
• You’ll usually need some communications mechanism to connect the two subsystems, and they could well be on separate servers. In this case, you have to decide where to place these communication links. If you have no access to the other subsystem, you may have to put the links between the FACADE and the other subsystem. However, if the FACADE can be integrated directly with the other subsystem, then a good option is to put the communication link between the ADAPTER and FACADE, because the protocol of the FACADE is presumably simpler than what it covers. There also will be cases where the entire ANTICORRUPTION LAYER can live with the other subsystem, placing communication links or distribution mechanisms between your subsystem and the SERVICES that make up the ANTICORRUPTION LAYER’s interface. These are implementation and deployment decisions to be made pragmatically. They have no bearing on the conceptual role of the ANTICORRUPTION LAYER.
• 如果您可以访问另一个子系统,您可能会发现对其进行一些重构可以简化您的工作。特别是,尽量为您将要使用的功能编写更明确的接口,如果可能,首先编写自动化测试。
• If you do have access to the other subsystem, you may find that a little refactoring over there can make your job easier. In particular, try to write more explicit interfaces for the functionality you’ll be using, starting with automated tests, if possible.
• 当集成需求非常广泛时,转换成本会大幅上升。为了简化转换过程,可能需要在设计系统模型时做出一些选择,使其更贴近外部系统。务必这样做。谨慎操作,但不能损害模型的完整性。只有当翻译难度过大时,才应选择性地采用这种方法。如果这种方法似乎是解决问题大部分重要部分的最佳方案,则可以考虑将子系统设计为符合模式(CONFORMIST),从而消除翻译。
• Where integration requirements are extensive, the cost of translation goes way up. It may be necessary to make choices in the model of the system under design that keep it closer to the external system, in order to make translation easier. Do this very carefully, without compromising the integrity of the model. It is only something to do selectively when translation difficulty gets out of hand. If this approach seems the most natural solution for much of the important part of the problem, consider making your subsystem a CONFORMIST pattern, eliminating translation.
• 如果另一个子系统很简单或者具有清晰的接口,则可能不需要FACADE。
• If the other subsystem is simple or has a clean interface, you may not need the FACADE.
•如果功能与两个子系统之间的关系密切相关,则可以将其添加到防腐层。例如,用于记录外部系统使用情况的审计跟踪,或用于调试对另一个接口调用的跟踪逻辑,都是很有用的功能。
• Functionality can be added to the ANTICORRUPTION LAYER if it is specific to the relationship of the two subsystems. An audit trail for use of the external system or trace logic for debugging the calls to the other interface are two useful features that come to mind.
请记住,防腐层是连接两个有界上下文的一种手段。通常,我们指的是由他人创建的系统;我们对该系统了解不全,控制力也有限。但这并非唯一需要在子系统之间设置缓冲层的情况。即使您自己设计的两个子系统基于不同的模型,使用防腐层连接它们也是合理的。在这种情况下,您通常可以完全控制两个子系统,并且通常可以使用简单的转换层。然而,如果两个有界上下文已经分道扬镳,但仍然需要进行一些功能集成,那么防腐层可以减少它们之间的摩擦。
Remember, an ANTICORRUPTION LAYER is a means of linking two BOUNDED CONTEXTS. Ordinarily, we are thinking of a system created by someone else; we have incomplete understanding of the system and little control over it. But that is not the only situation where you need a little padding between subsystems. There are even situations in which it makes sense to connect two subsystems of your own design with an ANTICORRUPTION LAYER, if they are based on different models. Presumably, in such a case, you will have full control over both sides and typically can use a simple translation layer. However, if two BOUNDED CONTEXTS have gone SEPARATE WAYS yet still have some need of functional integration, an ANTICORRUPTION LAYER can reduce the friction between them.
为了快速发布小型版本,我们将编写一个最小化的应用程序,用于设置发货单,然后通过转换层将其传递给旧系统,以进行预订和支持操作。由于我们构建转换层的目的就是为了保护我们的开发模型免受旧系统设计的影响,因此该转换层是一个防篡改层。
In order to have a small, quick first release, we will write a minimal application that can set up a shipment and then pass that to the legacy system through a translation layer for booking and support operations. Because we built the translation layer specifically to protect our developing model from the influence of the legacy design, this translation is an ANTICORRUPTION LAYER.
最初,反腐败层将接收代表货物的对象,对其进行转换,将其传递给旧系统并请求预订,然后捕获确认信息。将其转换回新设计的确认对象。这种隔离将使我们能够基本独立于旧应用程序开发新应用程序,尽管我们需要在转换方面投入相当多的资源。
Initially, the ANTICORRUPTION LAYER will accept the objects representing a shipment, convert them, pass them to the legacy system and request a booking, and then capture the confirmation and translate it back into the confirmation object of the new design. This isolation will allow us to develop our new application mostly independently of the old one, though we’ll have to invest quite a bit in translation.
随着每次版本更新,新系统可以根据后续决策,选择接管原有系统的更多功能,或者在不替换现有功能的情况下增加新的价值。这种灵活性,以及在逐步过渡的同时持续运行合并系统的能力,或许足以证明构建反腐败层所需的投入是值得的。
With each successive release, the new system can either take over more functions of the legacy or simply add new value without replacing existing capabilities, depending on later decisions. This flexibility, and the ability to continually operate the combined system while making a gradual transition, probably makes it worth the expense to build the ANTICORRUPTION LAYER.
为了保护边境免受邻近游牧部落的侵扰,早期中国人修建了长城。长城并非坚不可摧,但它既能规范与邻国的贸易往来,又能有效抵御外敌入侵和其他不良影响。两千年来,长城划定了一条边界,帮助中国农业文明在较少受到外部动荡干扰的情况下发展壮大。
To protect their frontiers from raids by neighboring nomadic warrior tribes, the early Chinese built the Great Wall. It was not an impenetrable barrier, but it allowed a regulated commerce with neighbors while providing an impediment to invasion and other unwanted influence. For two thousand years it defined a boundary that helped the Chinese agricultural civilization to define itself with less disruption from the chaos outside.
虽然没有长城,中国或许不会形成如此独特的文化,但长城的修建耗资巨大,至少使一个王朝破产,这很可能也是导致其灭亡的原因之一。孤立主义策略的益处必须与其代价相权衡。有时,我们需要务实,对这种模式进行审慎的调整,使其更好地适应外来模式。
Although China might not have become so distinct a culture without the Great Wall, the Wall’s construction was immensely expensive and bankrupted at least one dynasty, probably contributing to its fall. The benefits of isolation strategies must be balanced against their costs. There is a time to be pragmatic and make measured revisions to the model, so that it can fit more smoothly with foreign ones.
任何集成都会产生额外的开销,从单一限界上下文内的全面持续集成,到共享内核或客户/供应商开发团队等投入较少的方案,再到顺从者的片面性和反腐败层的防御姿态,莫不如此。集成可能非常有价值,但成本始终很高。我们应该确保它确实是必要的……
There is overhead involved in any integration, from full-on CONTINUOUS INTEGRATION inside a single BOUNDED CONTEXT, through the lesser commitments of SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS, to the one-sidedness of the CONFORMIST and the defensive posture of the ANTICORRUPTION LAYER. Integration can be very valuable, but it is always expensive. We should be sure it is really needed. . . .
我们必须严格界定需求范围。两组没有必然联系的功能可以彼此分离。
We must ruthlessly scope requirements. Two sets of functionality with no indispensable relationship can be cut loose from each other.
整合总是成本高昂,有时收益却很小。
Integration is always expensive. Sometimes the benefit is small.
除了协调团队的常规成本之外,整合还会带来妥协。能够满足特定需求的简单专用模型必须让位于能够应对所有情况的更抽象的模型。或许某些完全不同的技术可以轻松提供某些功能,但却难以整合。也可能某些团队难以相处,导致其他团队试图与他们合作时总是事与愿违。
In addition to the usual expense of coordinating teams, integration forces compromises. The simple specialized model that can satisfy a particular need must give way to the more abstract model that can handle all situations. Perhaps some completely different technology could provide certain features very easily, but it is difficult to integrate. Maybe some team is just so hard to get along with that nothing works very well when other teams try to collaborate with them.
在许多情况下,集成并不能带来显著的益处。如果两个功能部分互不调用对方的功能,或者不需要它们共同作用的对象进行交互,或者在运行过程中共享数据,那么即使通过转换层进行集成,也可能并非必要。仅仅因为某个功能在用例中相关,并不意味着它们必须集成。
In many circumstances, integration provides no significant benefit. If two functional parts do not call upon each other’s functionality, or require interactions between objects that are touched by both, or share data during their operations, then integration, even through a translation layer, may not be necessary. Just because features are related in a use case does not mean they must be integrated.
所以:
Therefore:
声明一个有界上下文,使其与其他上下文完全没有联系,从而允许开发人员在这个小范围内找到简单、专门的解决方案。
Declare a BOUNDED CONTEXT to have no connection to the others at all, allowing developers to find simple, specialized solutions within this small scope.
这些功能仍然可以组织在中间件或 UI 层中,但不会共享逻辑,并且通过转换层的数据传输量绝对要降到最低——最好是完全没有。
The features can still be organized in middleware or the UI layer, but there will be no sharing of logic, and an absolute minimum of data transfer through translation layers—preferably none.
一个项目团队着手开发一款全新的保险理赔软件,旨在将客服人员或理赔员所需的一切功能集成到一个系统中。一年后,团队成员却陷入了僵局。分析瘫痪加上前期在基础设施上的大量投入,导致他们无法向日益不耐烦的管理层展示任何成果。更严重的是,他们试图完成的工作规模之大,令他们不堪重负。
One project team had set out to develop new software for insurance claims that would integrate into one system everything a customer service agent or a claims adjuster needed. After a year of effort, team members were stuck. A combination of analysis paralysis and a major up-front investment in infrastructure had found them with nothing to show an increasingly impatient management. More seriously, the scope of what they were trying to do was overwhelming them.
新项目经理召集所有人开会,花了一周时间制定新计划。首先,他们列出了需求清单,并尝试评估其难度和重要性。他们毫不留情地剔除了那些难度高和不重要的需求。然后,他们开始整理剩下的清单。那一周,会议室里做出了许多明智的决定,但最终,只有一项被证明是至关重要的。后来,大家意识到,有些功能集成后几乎没有任何附加价值。例如,理赔员需要访问一些现有数据库,但他们目前的访问方式非常不便。虽然用户需要这些数据,但拟议软件系统的其他功能都不会用到它们。
A new project manager forced everyone into a room for a week to form a new plan. First they made lists of requirements and tried to estimate their difficulty and assign importance. They ruthlessly chopped the difficult and unimportant ones. Then they started to bring order to the remaining list. Many smart decisions were made in that room that week, but in the end, only one turned out to be important. At some point it was recognized that there were some features for which integration provided little added value. For example, adjusters needed access to some existing databases, and their current access was very inconvenient. But, although the users needed to have this data, none of the other features of the proposed software system would use it.
团队成员提出了多种便捷访问方案。例如,可以将关键报告导出为 HTML 格式并上传到内网。另一种方案是,可以使用标准软件包编写专门的查询语句,提供给理赔员。所有这些功能都可以通过在内网页面上组织链接或在用户桌面上放置按钮来实现。
Team members proposed various ways of providing easy access. In one case, a key report could be exported as HTML and placed on the intranet. In another case, adjusters could be provided with a specialized query written using a standard software package. All these functions could be integrated by organizing links on an intranet page or by placing buttons on the user’s desktop.
团队启动了一系列小型项目,这些项目仅尝试从同一菜单启动,并未进行更深入的集成。几个有价值的功能几乎在一夜之间就交付了。剔除这些无关功能后,剩下的精简需求一度让人看到了主应用程序交付的希望。
The team launched a set of small projects that attempted no more integration than launching from the same menu. Several valuable capabilities were delivered almost overnight. Dropping the baggage of these extraneous features left a distilled set of requirements that seemed for a while to give hope for delivery of the main application.
事情原本可以朝着那个方向发展,但可惜的是,团队又回到了老样子,再次陷入了停滞不前的状态。最终,他们留下的唯一遗产,竟然是那些各自发展、分崩离析的小应用程序。
It could have gone that way, but unfortunately the team slipped back into old habits. They paralyzed themselves again. In the end, their only legacy turned out to be those small applications that had gone their SEPARATE WAYS.
各自为政会排除一些选择。虽然持续重构最终可以撤销任何决定,但要合并完全独立开发的模型却十分困难。如果最终还是需要集成,那么转换层必不可少,而且可能会非常复杂。当然,无论如何,你最终都会面临这个问题。
Taking SEPARATE WAYS forecloses some options. Although continuous refactoring can eventually undo any decision, it is hard to merge models that have developed in complete isolation. If integration turns out to be needed after all, translation layers will be necessary and may be complex. Of course, this is something you will face anyway.
现在,让我们回到更具合作关系的话题,看看如何扩大一体化的规模……
Now, turning back to more cooperative relationships, let’s look at ways to scale up integration. . . .
通常情况下,对于每个有界上下文,您需要为上下文之外的每个组件定义一个转换层,以便与之集成。如果集成是一次性的,这种为每个外部系统插入转换层的方法可以最大限度地减少模型损坏。但是,当您的子系统需求量很大时,您可能需要更灵活的方法。
Typically for each BOUNDED CONTEXT, you will define a translation layer for each component outside the CONTEXT with which you have to integrate. Where integration is one-off, this approach of inserting a translation layer for each external system avoids corruption of the models with a minimum of cost. But when you find your subsystem in high demand, you may need a more flexible approach.
当一个子系统需要与其他多个子系统集成时,为每个子系统定制翻译器会拖慢团队的进度。需要维护的内容越来越多,变更发生时需要处理的问题也越来越多。
When a subsystem has to be integrated with many others, customizing a translator for each can bog down the team. There is more and more to maintain, and more and more to worry about when changes are made.
团队可能一直在重复做同样的事情。如果子系统具有任何内在逻辑性,那么或许可以将其描述为一组服务,这些服务满足其他子系统的共同需求。
The team may be doing the same thing again and again. If there is any coherence to the subsystem, it is probably possible to describe it as a set of SERVICES that cover the common needs of other subsystems.
设计一个足够清晰、易于多个团队理解和使用的协议难度很大,因此只有当子系统的资源可以被描述为一组统一的“服务”且存在大量集成时,它才能发挥作用。在这种情况下,它甚至可以决定系统是进入维护模式还是继续开发。
It is a lot harder to design a protocol clean enough to be understood and used by multiple teams, so it pays off only when the subsystem’s resources can be described as a cohesive set of SERVICES and when there are a significant number of integrations. Under those circumstances, it can make the difference between maintenance mode and continuing development.
所以:
Therefore:
定义一个协议,允许将你的子系统作为一组服务进行访问。开放该协议,以便所有需要与你集成的人员都可以使用它。增强和扩展该协议以处理新的集成需求,但个别团队有特殊需求的情况除外。在这种情况下,可以使用一次性转换器来增强协议,以保持共享协议的简洁性和一致性。
Define a protocol that gives access to your subsystem as a set of SERVICES. Open the protocol so that all who need to integrate with you can use it. Enhance and expand the protocol to handle new integration requirements, except when a single team has idiosyncratic needs. Then, use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent.
这种通信形式化意味着需要一些共享的模型词汇——这是服务接口的基础。因此,其他子系统会与开放主机(OPEN HOST)的模型耦合,其他团队也被迫学习主机团队使用的特定方言。在某些情况下,使用一种广为人知的公开语言(PUBLISHED LANGUAGE)作为交换模型可以降低耦合度并简化理解。
This formalization of communication implies some shared model vocabulary—the basis of the SERVICE interfaces. As a result, the other subsystems become coupled to the model of the OPEN HOST, and other teams are forced to learn the particular dialect used by the HOST team. In some situations, using a well-known PUBLISHED LANGUAGE as the interchange model can reduce coupling and ease understanding. . . .
两个有界上下文模型之间的转换需要一种共同的语言。
The translation between the models of two BOUNDED CONTEXTS requires a common language.
当两个领域模型必须共存且信息需要在它们之间传递时,转换过程本身就会变得复杂且难以记录和理解。如果我们正在构建一个新系统,通常会认为新模型是最佳选择,因此会考虑直接将其转换为新模型。但有时我们需要增强一组旧系统并尝试将它们集成起来。在这种情况下,选择一个混乱的模型可能只是两害相权取其轻。
When two domain models must coexist and information must pass between them, the translation process itself can become complex and hard to document and understand. If we are building a new system, we will typically believe that our new model is the best available, and so we will think in terms of translating directly into it. But sometimes we are enhancing a set of older systems and trying to integrate them. Choosing one messy model over the other may be choosing the lesser of two evils.
另一种情况:当企业之间需要交换信息时,它们该如何操作?期望一方采用另一方的领域模型不仅不现实,而且对双方都可能不利。领域模型是为了解决用户问题而开发的;这样的模型可能包含一些不必要的功能,使与其他系统的通信变得复杂。此外,如果将某个应用程序的底层模型用作通信媒介,则该模型无法自由更改以满足新的需求,而必须非常稳定才能支持持续的通信功能。
Another situation: When businesses want to exchange information with one another, how do they do it? Not only is it unrealistic to expect one to adopt the domain model of the other, it may be undesirable for both parties. A domain model is developed to solve problems for its users; such a model may contain features that needlessly complicate communication with another system. Also, if the model underlying one of the applications is used as the communications medium, it cannot be changed freely to meet new needs, but must be very stable to support the ongoing communication role.
直接将现有领域模型转换为数据交换语言可能并非良策。这些模型可能过于复杂或结构混乱,而且很可能缺乏文档。如果将其中一种模型用作数据交换语言,它实际上就会被冻结,无法响应新的开发需求。
Direct translation to and from the existing domain models may not be a good solution. Those models may be overly complex or poorly factored. They are probably undocumented. If one is used as a data interchange language, it essentially becomes frozen and cannot respond to new development needs.
开放主机服务使用标准化的多方集成协议。它采用领域模型进行系统间的数据交换,即使这些系统内部可能并不使用该模型。我们更进一步,发布这种语言,或者找到一种已经发布的语言。所谓发布,是指该语言可供可能对此感兴趣的社区成员使用,并且文档齐全,足以保证不同的解释版本之间兼容。
The OPEN HOST SERVICE uses a standardized protocol for multiparty integration. It employs a model of the domain for interchange between systems, even though that model may not be used internally by those systems. Here we go a step further and publish that language, or find one that is already published. By publish I simply mean that the language is readily available to the community that might be interested in using it, and is sufficiently documented to allow independent interpretations to be compatible.
最近,电子商务界对一项新技术——可扩展标记语言(XML)——感到非常兴奋,这项技术有望……XML极大地简化了数据交换。XML的一个非常有价值的特性是,通过文档类型定义(DTD)或XML模式,XML允许正式定义一种专门的领域语言,数据可以转换成这种语言。一些行业组织已经开始成立,旨在为各自的行业定义一个统一的标准DTD,以便例如化学式信息或基因编码等数据可以在多个参与方之间进行交流。本质上,这些组织正在以语言定义的形式创建一个共享的领域模型。
Recently, the world of e-commerce has become very excited about a new technology: Extensible Markup Language (XML) promises to make interchange of data much easier. A very valuable feature of XML is that, through the document type definition (DTD) or through XML schemas, XML allows the formal definition of a specialized domain language into which data can be translated. Industry groups have begun to form for the purpose of defining a single standard DTD for their industry so that, say, chemical formula information or genetic coding can be communicated between many parties. Essentially these groups are creating a shared domain model in the form of a language definition.
所以:
Therefore:
使用有完善文档的共享语言,以表达必要的领域信息作为通用的沟通媒介,并根据需要进行该语言之间的翻译。
Use a well-documented shared language that can express the necessary domain information as a common medium of communication, translating as necessary into and out of that language.
语言不必从零开始创建。多年前,我受雇于一家公司,该公司有一款用 Smalltalk 编写的软件产品,使用 DB2 存储数据。该公司希望能够灵活地将软件分发给没有 DB2 许可证的用户,因此委托我构建一个与 Btrieve 的接口。Btrieve 是一款轻量级的数据库引擎,提供免费的运行时分发许可证。Btrieve 并非完全关系型数据库,但我的客户只使用了 DB2 的一小部分功能,并且处于这两个数据库的最低共同点。该公司开发人员在 DB2 的基础上构建了一些关于对象存储的抽象层。我决定利用这些抽象层作为我的 Btrieve 组件的接口。
The language doesn’t have to be created from scratch. Many years ago, I was contracted by a company that had a software product written in Smalltalk that used DB2 to store its data. The company wanted the flexibility to distribute the software to users without a DB2 license and contracted me to build an interface to Btrieve, a lighter-weight database engine that had a free runtime distribution license. Btrieve is not fully relational, but my client was using only a small part of DB2’s power and was within the lowest common denominator of the two databases. The company’s developers had built on top of DB2 some abstractions that were in terms of the storage of objects. I decided to use this work as the interface for my Btrieve component.
这种方法确实奏效了。软件与客户的系统顺利集成。然而,由于客户设计中缺乏对持久化对象抽象概念的正式规范或文档,我花了不少功夫才弄清新组件的需求。此外,也几乎没有机会将该组件复用于将其他应用程序从 DB2 迁移到 Btrieve。而且,新软件进一步强化了公司原有的持久化模型,因此重构该持久化对象模型将更加困难。
This approach did work. The software smoothly integrated with my client’s system. However, the lack of a formal specification or documentation of the abstractions of persistent objects in the client’s design meant a lot of work for me to figure out the requirements of the new component. Also, there wasn’t much opportunity to reuse the component to migrate some other application from DB2 to Btrieve. And the new software more deeply entrenched the company’s model of persistence, so that refactoring that model of persistent objects would have been even more difficult.
更好的方法可能是先确定该公司正在使用的 DB2 接口子集,然后提供支持。DB2 接口由 SQL 和一些专有协议组成。虽然它非常复杂,但接口规范严谨,文档齐全。这种复杂性原本是……由于仅使用了接口的一小部分,因此问题得以缓解。如果开发一个组件来模拟 DB2 接口的必要子集,那么只需明确指出该子集,即可非常有效地为开发人员提供文档。该组件集成的应用程序已经知道如何与 DB2 通信,因此几乎不需要额外的工作。未来对持久层的重新设计将仅限于使用 DB2 子集,就像增强之前一样。
A better way might have been to identify the subset of the DB2 interface that the company was using and then support that. The interface of DB2 is made up of SQL and a number of proprietary protocols. Although it is very complex, the interface is tightly specified and thoroughly documented. The complexity would have been mitigated because only a small subset of the interface was being used. If a component had been developed that emulated the necessary subset of the DB2 interface, it could have been very effectively documented for developers simply by identifying the subset. The application it was integrated into already knew how to talk to DB2, so little additional work would have been needed. Future redesign of the persistence layer would have been constrained only to the use of the DB2 subset, just as before the enhancement.
DB2 接口就是一个已发布语言的例子。在这种情况下,两个模型虽然不在业务领域内,但所有原则都同样适用。由于协作中的一个模型已经是已发布语言,因此无需引入第三种语言。
The DB2 interface is an example of a PUBLISHED LANGUAGE. In this case, the two models are not in the business domain, but all the principles apply just the same. Because one of the models in the collaboration is already a PUBLISHED LANGUAGE, there is no need to introduce a third language.
在工业界和学术界,无数程序被用于编目、分析和处理化学式。数据交换一直很困难,因为几乎每个程序都使用不同的领域模型来表示化学结构。当然,它们中的大多数都是用诸如 FORTRAN 之类的语言编写的,而这些语言本身也无法充分表达领域模型。因此,每当有人想要共享数据时,他们都必须深入了解对方系统的数据库细节,并制定某种转换方案。
Innumerable programs are used to catalog, analyze, and manipulate chemical formulas in industry and academia. Exchanging data has always been difficult, because almost every program uses a different domain model to represent chemical structures. And of course, most of them are written in languages, such as FORTRAN, that do not express the domain model very fully anyway. Whenever anyone wanted to share data, they had to unravel the details of the other system’s database and work out some sort of translation scheme.
于是出现了化学标记语言 (CML),它是 XML 的一种方言,旨在作为该领域的通用交换语言,由代表学术界和工业界的团体开发和管理 ( Murray-Rust 等人,1995 年)。
Enter the Chemical Markup Language (CML), a dialect of XML intended as a common interchange language for this domain, developed and managed by a group representing academics and industry (Murray-Rust et al. 1995).
化学信息非常复杂多样,而且随着新发现的不断涌现而持续变化。因此,人们开发了一种语言来描述基本信息,例如有机和无机分子的化学式、蛋白质序列、光谱或物理量。
Chemical information is very complex and diverse, and it changes all the time with new discoveries. So they developed a language that could describe the basics, such as the chemical formulas of organic and inorganic molecules, protein sequences, spectra, or physical quantities.
现在CML语言已经发布,我们可以开发出以前只能用于单一数据库、根本不值得费力编写的工具。例如,我们开发了一款名为JUMBO Browser的Java应用程序,它可以创建以CML格式存储的化学结构的图形视图。因此,如果您将数据转换为CML格式,就可以使用这类可视化工具。
Now that the language has been published, tools can be developed that would never have been worth the trouble to write before, when they would have only been usable for one database. For example, a Java application, called the JUMBO Browser, was developed that creates graphical views of chemical structures stored in CML. So if you put your data in the CML format, you’ll have access to such visualization tools.
事实上,CML 通过使用 XML 这种“公开的元语言”获得了双重优势。人们对 XML 的熟悉程度降低了 CML 的学习难度;各种现成的工具(例如解析器)简化了 CML 的实现;大量关于 XML 处理各个方面的书籍也为文档编写提供了帮助。
In fact, CML gained a double advantage by using XML, a sort of “published meta-language.” The learning curve of CML is flattened by people’s familiarity with XML; the implementation is eased by various off-the-shelf tools, such as parsers; and documentation is helped by the many books written on all aspects of handling XML.
以下是CML的一小部分示例。对于像我这样的非专业人士来说,它只能粗略地理解,但其原理是清晰的。
Here is a tiny sample of CML. It is only vaguely intelligible to nonspecialists like myself, but the principle is clear.
<CML.ARR ID="array3" EL.TYPE=FLOAT NAME="ATOMIC ORBITAL ELECTRON POPULATIONS" SIZE=30 GLO.ENT=CML.THE.AOEPOPS>
1.17947 0.95091 0.97175 1.00000 1.17947 0.95090 0.97174 1.00000
1.17946 0.98215 0.94049 1.00000 1.17946 0.95091 0.97174 1.00000
1.17946 0.95091 0.97174 1.00000 1.17946 0.98215 0.94049 1.00000
0.89789 0.89790 0.89789 0.89789 0.89790 0.89788
</CML.ARR>
六个来自印度斯坦
、求知欲旺盛的男子,(尽管他们都是盲人)
前去观赏大象,希望通过观察来满足自己的好奇心。第一个人走近大象,不小心撞到了它宽阔结实的身子上,立刻大叫起来:“老天保佑!这大象简直像一堵墙!” ……第三个人走近大象,碰巧抓住了它扭动的鼻子,于是大胆地说:“我看,”他说,“这大象简直像一条蛇。”第四个人伸出急切的手,摸了摸大象的膝盖。“这神奇的野兽最像什么,显而易见,”他说,“很明显,这大象简直像一棵树!” ……第六个刚开始摸索那头野兽,就抓住了垂到他视线范围内的摆动的尾巴,说道:“我明白了,这大象真像根绳子!”于是,这些印度斯坦人就此争论不休,每个人都固执己见,虽然每个人都有几分道理,但所有人都错了 !
It was six men of Indostan
To learning much inclined,
Who went to see the Elephant
(Though all of them were blind),
That each by observation
Might satisfy his mind.
The First approached the Elephant,
And happening to fall
Against his broad and sturdy side,
At once began to bawl:
"God bless me! but the Elephant
Is very like a wall!"
. . .
The Third approached the animal,
And happening to take
The squirming trunk within his hands,
Thus boldly up and spake:
"I see," quoth he, "the Elephant
Is very like a snake."
The Fourth reached out his eager hand,
And felt about the knee.
"What most this wondrous beast is like
Is mighty plain," quoth he;
"’Tis clear enough the Elephant
Is very like a tree!"
. . .
The Sixth no sooner had begun
About the beast to grope,
Than, seizing on the swinging tail
That fell within his scope,
"I see," quoth he, "the Elephant
Is very like a rope!"
And so these men of Indostan
Disputed loud and long,
Each in his own opinion
Exceeding stiff and strong,
Though each was partly in the right,
And all were in the wrong!
. . .
——摘自约翰·戈弗雷·萨克斯(1816-1887)的《盲人摸象》,该故事取材于印度教经典《乌达那经》。
—From “The Blind Men and the Elephant,” by John Godfrey Saxe (1816–1887), based on a story in the Udana, a Hindu text
根据他们与大象互动的目标,即使盲人摸象者对大象的本质没有完全共识,他们仍然可能取得进展。如果不需要整合,那么模型是否统一也无关紧要。如果需要整合,他们或许不必就大象的定义达成一致,但仅仅意识到彼此存在分歧就能让他们受益匪浅。这样,至少他们不会在不知不觉中各说各话。
Depending on their goals in interacting with the elephant, the various blind men may still be able to make progress, even if they don’t fully agree on the nature of the elephant. If no integration is required, then it doesn’t matter that the models are not unified. If they require some integration, they may not actually have to agree on what an elephant is, but they will get a lot of value from merely recognizing that they don’t agree. This way, at least they don’t unknowingly talk at cross-purposes.
图 14.9中的图表是盲人摸象者们构建的大象模型的 UML 表示。在建立了各自的限界上下文之后,情况已经足够清晰,他们可以找到一种方法来就他们共同关心的几个方面进行沟通:比如大象的位置。
The diagrams in Figure 14.9 are UML representations of the models the blind men have formed of the elephant. Having established separate BOUNDED CONTEXTS, the situation is clear enough for them to work out a way to communicate with each other about the few aspects they care about in common: the location of the elephant, perhaps.
图 14.9. 四种情况:无整合
Figure 14.9. Four contexts: no integration
图 14.10. 四种情境:最小整合
Figure 14.10. Four contexts: minimal integration
随着盲人们想要分享更多关于大象的信息,共享一个单一的有限上下文的价值也随之提升。然而,整合这些不同的模型却是一项挑战。他们中没有人会放弃自己的模型而接受其他人的模型。毕竟,摸到大象尾巴的人知道大象不像树,对他来说,树的模型毫无意义,也毫无用处。整合多个模型几乎总是意味着创建一个新的模型。
As the blind men want to share more information about the elephant, the value of sharing a single BOUNDED CONTEXT goes up. But unifying the disparate models is a challenge. None of them is likely to give up his model and adopt one of the others. After all, the man who touched the tail knows the elephant is not like a tree, and that model would be meaningless and useless to him. Unifying multiple models almost always means creating a new model.
凭借一些想象力和持续的讨论(可能还会很激烈),盲人们最终可能会意识到他们拥有一直以来,我们都在描述和模拟一个更大整体的不同部分。在许多情况下,部分与整体的统一可能并不需要太多额外的工作。至少,整合的第一阶段只需要弄清楚各个部分之间的关系。在某些情况下,将大象视为一堵由树干支撑的墙,一端系着绳子,另一端系着蛇,可能就足够了。
With some imagination and continued discussion (probably heated), the blind men could eventually recognize that they have been describing and modeling different parts of a larger whole. For many purposes, a part-whole unification may not require much additional work. At least the first stage of integration only requires figuring out how the parts are related. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other.
图 14.11. 一种情况:粗略整合
Figure 14.11. One context: crude integration
各种大象模型的统一比大多数此类合并要容易得多。然而,当两个模型纯粹描述整体的不同部分时,这种情况实属例外,尽管这通常是两者差异的一个方面。当两个模型以不同的方式看待同一部分时,问题就变得更加复杂。如果两个人触摸象鼻,一个将其描述为蛇,另一个将其描述为消防水带,他们就会遇到更大的困难。他们都无法接受对方的模型,因为它与自己的经验相矛盾。事实上,他们需要一种新的抽象概念,既包含蛇的“生命力”,又包含消防水带喷水的功能,但同时又要摒弃先前模型中不恰当的含义,例如预期象鼻可能带有毒牙,或者可以与身体分离并卷入消防车的隔间。
The unification of the various elephant models is easier than most such mergers. Unfortunately, it is the exception when two models purely describe different parts of the whole, although this is often one aspect of the difference. Matters are more difficult when two models are looking at the same part in a different way. If two men had touched the trunk and one described it as a snake and the other described it as a fire hose, they would have had more difficulty. Neither can accept the other’s model, because it contradicts his own experience. In fact, they need a new abstraction that incorporates the “aliveness” of a snake with the water-shooting functionality of a fire hose, but one that leaves out the inapt implications of the first models, such as the expectation of possibly venomous fangs, or the ability to be detached from the body and rolled up into a compartment in a fire truck.
尽管我们已将各个部分组合成一个整体,但最终得到的模型仍然粗糙。它缺乏连贯性,无法体现底层领域的轮廓。新的见解可能会在持续改进的过程中引导我们构建更深层次的模型。新的应用需求也可能迫使我们转向更深层次的模型。如果大象开始移动,“树状”理论就不成立了,而我们这些盲目的建模者或许会突破思维的局限,领悟到“腿”的概念。
Even though we have combined the parts into a whole, the resulting model is crude. It is incoherent, lacking any sense of following contours of an underlying domain. New insights could lead to a deeper model in a process of continuous refinement. New application requirements can also force the move to a deeper model. If the elephant starts moving, the “tree” theory is out, and our blind modelers may break through to the concept of “legs.”
图 14.12. 一种情境:更深层次的模型
Figure 14.12. One context: deeper model
第二次模型整合往往会剔除各个模型中偶然或错误的部分,并创造出新的概念——在本例中是“动物”,它由“象鼻”、“腿”、“身体”和“尾巴”四个部分组成——每个部分都有其自身的属性,并与其他部分有着清晰的联系。成功的模型整合在很大程度上取决于极简主义。大象的鼻子既比蛇的鼻子多,也比蛇的鼻子少,但“少”这一点可能比“多”这一点更重要。与其拥有错误的毒牙特征,不如缺少喷水的能力。
This second pass of model integration tends to slough off incidental or incorrect aspects of the individual models and creates new concepts—in this case, “animal” with parts “trunk,” “leg,” “body,” and “tail”—each of which has its own properties and clear relationships to other parts. Successful model unification, to a large extent, hinges on minimalism. An elephant trunk is both more and less than a snake, but the “less” is probably more important than the “more.” Better to lack the water-spewing ability than to have an incorrect poison-fang feature.
如果目标仅仅是找到大象,那么只需在各个模型对位置的表达方式之间进行转换即可。当需要更深入的整合时,统一模型不必在第一版中就完全成熟。在某些情况下,将大象视为一堵由树干支撑的墙,一端系着绳子,另一端系着蛇,或许就足够了。之后,随着新需求的出现以及理解和沟通的加深,可以对模型进行深化和完善。
If the goal is simply to find the elephant, then translating between each model’s expression of location will do. When more integration is needed, the unified model doesn’t have to reach full maturity in the first version. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other. Later, driven by new requirements and by improved understanding and communication, the model can be deepened and refined.
认识到存在多个相互冲突的领域模型,其实就是直面现实。通过明确定义每个模型适用的上下文,可以维护每个模型的完整性,并清晰地看到在两者之间创建的任何特定接口的影响。盲人无法看到整头大象,但如果他们能意识到自身认知的局限性,问题就能迎刃而解。
Recognizing multiple, clashing domain models is really just facing reality. By explicitly defining a context within which each model applies, you can maintain the integrity of each and clearly see the implications of any particular interface you want to create between the two. There is no way for the blind men to see the whole elephant, but their problem would be manageable if only they recognized the incompleteness of their perception.
绘制情境图时,务必始终反映当前情况。然而,绘制完成后,您很可能想要改变现状。现在,您可以开始有意识地选择情境边界和关系。以下是一些指导原则。
It is important always to draw the CONTEXT MAP to reflect the current situation at any given time. Once that’s done, though, you may very well want to change that reality. Now you can begin to consciously choose CONTEXT boundaries and relationships. Here are some guidelines.
首先,团队必须决定如何定义“限界上下文”(BOUNDED CONTEXTS)以及它们之间的关系。这些决定必须由团队做出,或者至少必须传达给整个团队,并确保每个人都理解。事实上,这类决定通常需要团队以外的各方达成共识。从实际角度来看,是否扩展或划分“限界上下文”的决定,应基于独立团队行动的价值与直接且深入整合的价值之间的成本效益权衡。在实践中,团队之间的政治关系往往决定了系统的整合方式。由于汇报结构的原因,技术上有利的统一可能无法实现。管理层可能会强制进行笨拙的合并。你不可能总是如愿以偿,但至少你可以评估并沟通由此产生的成本,并采取措施来降低成本。首先要绘制一个切合实际的上下文图,并在选择转型方案时保持务实的态度。
First, teams have to make decisions about where to define BOUNDED CONTEXTS and what sort of relationships to have between them. Teams have to make these decisions, or at least the decisions have to be propagated to the entire team and understood by everyone. In fact, such decisions often involve agreements beyond your own team. On the merits, decisions about whether to expand or to partition BOUNDED CONTEXTS should be based on the cost-benefit trade-off between the value of independent team action and the value of direct and rich integration. In practice, political relationships between teams often determine how systems are integrated. A technically advantageous unification may be impossible because of reporting structure. Management may dictate an unwieldy merger. You won’t always get what you want, but at least you may be able to assess and communicate something of the cost incurred, and take steps to mitigate it. Start with a realistic CONTEXT MAP and be pragmatic in choosing transformations.
在进行软件项目开发时,我们主要关注的是团队正在修改的系统部分(“设计中的系统”),其次才是它将与之通信的系统。通常情况下,设计中的系统会被划分为一到两个限界上下文(BOUNDED CONTRECTS),主要开发团队将在这些上下文中进行开发,可能还会有一两个上下文起到辅助作用。除此之外,我们还要考虑这些上下文与外部系统之间的关系。以上是一个简单的典型示例,旨在让您对可能遇到的情况有一个大致的了解。
When we are working on a software project, we are interested primarily in the parts of the system our team is changing (the “system under design”) and secondarily in the systems it will communicate with. In a typical case, the system under design is going to get carved into one or two BOUNDED CONTEXTS that the main development teams will be working on, perhaps with another CONTEXT or two in a supporting role. In addition to that are the relationships between these CONTEXTS and the external systems. This is a simple, typical view, to give some rough expectation for what you are likely to encounter.
我们确实是我们所处的主要环境的一部分,这一点必然会反映在我们的环境地图中。如果我们意识到这种偏见,并且注意何时超出该环境地图的适用范围,这就不成问题。
We really are part of that primary CONTEXT we are working in, and that is bound to be reflected in our CONTEXT MAP. This isn’t a problem if we are aware of the bias and are mindful of when we step outside the limits of that MAP’s applicability.
情境千变万化,划定“有界语境”边界的方法也无穷无尽。但是通常,斗争的目的是平衡以下几种力量中的某些部分:
There are an unlimited variety of situations and an unlimited number of options for drawing the boundaries of BOUNDED CONTEXTS. But typically the struggle is to balance some subset of the following forces:
倾向于更大的有界背景
Favoring Larger BOUNDED CONTEXTS
• 当更多用户任务通过统一模型处理时,用户任务之间的流程会更加顺畅。
• Flow between user tasks is smoother when more is handled with a unified model.
• 理解一个连贯的模型比理解两个不同的模型加上映射关系要容易得多。
• It is easier to understand one coherent model than two distinct ones plus mappings.
• 两种模型之间的转换可能很困难(有时是不可能的)。
• Translation between two models can be difficult (sometimes impossible).
• 共同的语言有助于团队进行清晰的沟通。
• Shared language fosters clear team communication.
倾向于较小的有界上下文
Favoring Smaller BOUNDED CONTEXTS
• 开发人员之间的沟通开销减少。
• Communication overhead between developers is reduced.
•团队规模和代码库规模较小时,持续集成更容易。
• CONTINUOUS INTEGRATION is easier with smaller teams and code bases.
• 更大的背景可能需要更通用的抽象模型,这就需要一些稀缺的技能。
• Larger contexts may call for more versatile abstract models, requiring skills that are in short supply.
• 不同的模型可以满足特殊需求,或者涵盖专业用户群体的术语,以及普遍语言的专业方言。
• Different models can cater to special needs or encompass the jargon of specialized groups of users, along with specialized dialects of the UBIQUITOUS LANGUAGE.
在不同的有界上下文之间进行深度功能集成是不切实际的。集成仅限于一个模型中那些可以用另一个模型严格表述的部分,即使是这种程度的集成也可能需要相当大的努力。当两个系统之间只有少量接口时,这种做法是合理的。
Deep integration of functionality between different BOUNDED CONTEXTS is impractical. Integration is limited to those parts of one model that can be rigorously stated in terms of the other model, and even this level of integration may take considerable effort. This makes sense when there will be a small interface between two systems.
最好从最简单的决策开始。某些子系统显然不属于正在开发的系统的任何限定上下文。例如,您不会立即替换的大型遗留系统,以及为您提供服务的外部系统。需要。您可以立即识别这些需求,并准备将它们从您的设计中分离出来。
It is best to start with the easiest decisions. Some subsystems will clearly not be in any BOUNDED CONTEXT of the system under development. Examples would be major legacy systems that you are not immediately replacing and external systems that provide services you’ll need. You can identify these immediately and prepare to segregate them from your design.
在此,我们必须谨慎对待我们的假设。虽然将每个系统都视为构成其自身有限上下文(BOUNDED CONTEXT )很方便,但大多数外部系统仅勉强符合该定义。首先,有限上下文的定义在于将模型统一到特定边界内的意图。您可能负责维护遗留系统,在这种情况下,您可以声明此意图;或者,遗留系统团队可能协调良好,并正在执行某种形式的非正式持续集成(CONTINUOUS INTEGATION),但切勿想当然。务必进行核查,如果开发集成度不高,则更应格外谨慎。在此类系统的不同部分发现语义矛盾并不罕见。
Here we must be careful about our assumptions. It is convenient to think of each of these systems as constituting its own BOUNDED CONTEXT, but most external systems only weakly meet the definition. First, a BOUNDED CONTEXT is defined by an intention to unify the model within certain boundaries. You may have control of maintenance of the legacy system, in which case you can declare the intention, or the legacy team may be well coordinated and be carrying out an informal form of CONTINUOUS INTEGRATION, but don’t take it for granted. Check into it, and if the development is not well integrated, be particularly cautious. It is not unusual to find semantic contradictions in different parts of such systems.
这里可以应用三种模式。首先,考虑采用独立路径。没错,如果不需要集成,你就不会考虑这些路径。但务必仔细考虑。仅仅让用户轻松访问两个系统就足够了吗?集成既昂贵又分散精力,所以尽可能减轻项目的负担。
There are three patterns that can apply here. First, to consider SEPARATE WAYS. Yes, you wouldn’t have included them if you didn’t need integration. But be really sure. Would it be sufficient to give the user easy access to both systems? Integration is expensive and distracting, so unburden your project as much as you can.
如果集成至关重要,您可以选择两个极端:循规蹈矩或反腐败层。循规蹈矩并非易事。您的创造力和新功能的选择都将受到限制。在构建一个全新的大型系统时,遵循现有系统或外部系统的模式不太可能实际(毕竟,您为什么要构建一个新系统?)。然而,对于大型系统的外围扩展,如果该系统仍将保持主导地位,那么坚持沿用现有模式或许是合适的。例如,轻量级决策支持工具通常使用 Excel 或其他简单工具编写。如果您的应用程序确实是对现有系统的扩展,并且您与该系统的接口规模庞大,那么上下文之间的转换工作量可能比应用程序本身的功能还要大。即使您已将自己置于另一个系统的有限上下文中,仍然有一些空间可以进行优秀的设计工作。如果另一个系统背后存在一个可辨识的领域模型,那么只要你这样做,你就可以通过使该模型比旧系统中的更明确来改进你的实现。严格遵循旧模型。如果你决定采用遵循旧模型的设计,就必须全心全意地执行。你只能进行扩展,而不能对现有模型进行任何修改。
If the integration is really essential, you can choose between two extremes: CONFORMIST or ANTICORRUPTION LAYER. It is not fun to be a CONFORMIST. Your creativity and your options for new functionality will be limited. In building a major new system, it is unlikely to be practical to adhere to the model of a legacy or external system (after all, why are you building a new system?). However, sticking with the legacy model may be appropriate in the case of peripheral extensions to a large system that will continue to be the dominant system. Examples of this choice include the lightweight decision-support tools that are often written in Excel or other simple tools. If your application is really an extension to the existing system and your interface with that system is going to be large, the translation between CONTEXTS can easily be a bigger job than the application functionality itself. And there is still some room for good design work, even though you have placed yourself in the BOUNDED CONTEXT of the other system. If there is a discernable domain model behind the other system, you can improve your implementation by making that model more explicit than it was in the old system, just as long as you strictly conform to the old model. If you decide on a CONFORMIST design, you must do it wholeheartedly. You restrict yourself to extension only, with no modification of the existing model.
当设计中的系统的功能比现有系统的扩展更复杂,或者你与其他系统的接口很小,或者其他系统的设计非常糟糕时,你真的需要自己的有界上下文,这意味着构建一个转换层,甚至是防腐层。
When the functionality of the system under design is going to be more involved than an extension to an existing system, where your interface to the other system is small, or where the other system is very badly designed, you’ll really want your own BOUNDED CONTEXT, which means building a translation layer, or even an ANTICORRUPTION LAYER.
项目团队实际构建的软件就是正在设计的系统。您可以在此区域内声明限界上下文,并在每个限界上下文中应用持续集成,以保持它们的统一性。但是,您应该设置多少个限界上下文?它们之间应该存在怎样的关系?与外部系统相比,这些问题的答案并不那么简单明了,因为我们拥有更大的自由度和控制权。
The software your project team is actually building is the system under design. You can declare BOUNDED CONTEXTS within this zone and apply CONTINUOUS INTEGRATION within each to keep them unified. But how many should you have? What relationships should they have to each other? The answers are less cut and dried than with the external systems because we have more freedom and control.
这可能很简单:为整个设计系统创建一个单一的限界上下文。例如,对于一个人数少于十人的团队来说,如果他们负责开发高度相关的功能,这很可能是一个不错的选择。
It could be quite simple: a single BOUNDED CONTEXT for the entire system under design. For example, this would be a likely choice for a team of fewer than ten people working on highly interrelated functionality.
随着团队规模扩大,持续集成可能会变得困难(尽管我见过规模稍大的团队也能做到这一点)。您可以考虑使用共享内核,并将相对独立的功能集拆分到单独的限界上下文中,每个上下文的人员不超过十人。如果其中两个限界上下文之间的所有依赖关系都是单向的,则可以建立客户/供应商开发团队。
As the team grows larger, CONTINUOUS INTEGRATION may become difficult (although I have seen it maintained for somewhat larger teams). You may look for a SHARED KERNEL and break off relatively independent sets of functionality into separate BOUNDED CONTEXTS, each with fewer than ten people. If all of the dependencies between two of these go in one direction, you could set up CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
您可能已经意识到,两个团队的思维模式截然不同,导致他们的建模工作不断发生冲突。这可能是因为他们对模型的需求确实存在差异,也可能仅仅是背景知识的不同,或者项目所处的管理结构造成的。如果冲突的根源是您无法改变或不想改变的,您可以选择让模型各自独立运行。如果需要集成,两个团队可以共同开发和维护一个转换层,作为持续集成的唯一入口。这与和外部系统集成不同,在与外部系统集成时,需要使用反腐败层。 通常情况下,必须适应对方的系统现状,而对方却很少提供支持。
You may recognize that the mind-sets of two groups are so different that their modeling efforts constantly clash. It may be that they actually need quite different things from the model, it may be just a difference in background knowledge, or it may be a result of the management structure the project is embedded in. If the cause of the clash is something you can’t change, or don’t want to change, you may choose to allow the models to go SEPARATE WAYS. Where integration is needed, a translation layer can be developed and maintained jointly by the two teams as the single point of CONTINUOUS INTEGRATION. This is in contrast with integration with external systems, where the ANTICORRUPTION LAYER typically has to accommodate the other system as is and without much support from the other side.
一般来说,一个有界上下文对应一个团队。一个团队可以维护多个有界上下文,但多个团队很难(虽然并非不可能)在同一个上下文中协同工作。
Generally speaking, there is a correspondence of one team per BOUNDED CONTEXT. One team can maintain multiple BOUNDED CONTEXTS, but it is hard (though not impossible) for multiple teams to work on one together.
同一企业内部的不同团队往往会发展出各自独特的专业术语,这些术语之间可能存在差异。这些本地化的行话可能非常精准,并且是为满足特定需求而量身定制的。要改变这些术语(例如,通过推行标准化的企业级术语),需要进行大量的培训和分析来解决差异。即便如此,新的术语也未必比他们原有的、经过精心调整的术语更有效。
Different groups within the same business have often developed their own specialized terminologies, which may have diverged from one another. These local jargons may be very precise and tailored to their needs. Changing them (for example, by imposing a standardized, enterprise-wide terminology) requires extensive training and analysis to resolve the differences. Even then, the new terminology may not serve as well as the finely tuned version they already had.
您可以决定在独立的限定上下文中满足这些特殊需求,允许模型各自独立运行,但翻译层之间需要持续集成。通用语言的不同方言将围绕这些模型及其所基于的专业术语发展而来。如果两种方言有很多重叠之处,共享内核可以在提供所需专业化的同时,最大限度地降低翻译成本。
You may decide to cater to these special needs in separate BOUNDED CONTEXTS, allowing the models to go SEPARATE WAYS, except for CONTINUOUS INTEGRATION of translation layers. Different dialects of the UBIQUITOUS LANGUAGE will evolve around these models and the specialized jargon they are based on. If the two dialects have a lot of overlap, a SHARED KERNEL may provide the needed specialization while minimizing the translation cost.
在无需集成或集成程度相对有限的情况下,这种方法可以继续使用惯用术语,避免模型损坏。但它也存在一定的成本和风险。
Where integration is not needed, or is relatively limited, this allows continued use of customary terminology and avoids corruption of the models. It also has its costs and risks.
• 共同语言的丧失会减少沟通。
• The loss of shared language will reduce communication.
• 集成过程中会产生额外的开销。
• There is extra overhead in integration.
• 由于同一业务活动和实体的不同模式不断发展,因此会出现一些重复工作。
• There will be some duplication of effort, as different models of the same business activities and entities evolve.
但或许最大的风险在于,它可能成为反对变革的论据,并为任何古怪的、狭隘的模式辩护。为了满足特定需求,系统这一部分的定制程度究竟有多高?最重要的是,这部分用户的特定术语究竟有多大价值?你必须权衡团队自主行动的价值与翻译的风险,同时警惕那些毫无价值的术语变体。
But perhaps the biggest risk is that it can become an argument against change and a justification for any quirky, parochial model. How much do you need to tailor this individual part of the system to meet specialized needs? Most important, how valuable is the particular jargon of this user group? You have to weigh the value of more independent action of teams against the risks of translation, keeping an eye out for rationalizing terminology variations that have no value.
有时候,会出现一种能够统一这些不同语言并满足两类用户需求的深层模型。但关键在于,这种深层模型往往在生命周期的后期,经过大量的开发和知识积累之后才会出现,甚至可能根本不会出现。你无法预先计划深层模型的出现;你只能在机会出现时抓住它,改变策略,并进行重构。
Sometimes a deep model emerges that can unify these distinct languages and satisfy both groups. The catch is that deep models emerge later in the life cycle, after a lot of development and knowledge crunching, if at all. You can’t plan on a deep model; you just have to accept the opportunity when it arises, change your strategy, and refactor.
请注意,集成需求越大,翻译成本就越高。团队之间的协调,从对某个复杂对象的精准修改,到共享内核的整合,都能简化翻译过程,同时又无需完全统一。
Keep in mind that, where integration requirements are extensive, the cost of translation goes way up. Some coordination of the teams, from the pinpoint modifications of one object that has a complicated translation ranging up to a SHARED KERNEL, can make translation easier while still not requiring full unification.
协调复杂系统的打包和部署是一项看似枯燥乏味,实则难度极高的任务。选择有界上下文策略会对部署产生影响。例如,当客户/供应商团队部署新版本时,他们必须相互协调,确保发布的版本都经过共同测试。代码和数据迁移都必须能够适应这种组合。在分布式系统中,将不同上下文之间的转换层放在同一个进程中可能有助于避免多个版本共存的情况。
Coordinating the packaging and deployment of complex systems is one of those boring tasks that are almost always a lot harder than they look. The choice of BOUNDED CONTEXT strategy has an impact on the deployment. For example, when CUSTOMER/SUPPLIER TEAMS deploy new versions, they have to coordinate with each other to release versions that have been tested together. Both code and data migrations have to work in these combinations. In a distributed system, it may help to keep the translation layers between CONTEXTS together within a single process, so that you don’t have multiple versions coexisting.
即使部署单个有界上下文的组件,当数据迁移需要时间或分布式系统无法立即更新时,也会面临挑战,导致代码和数据的两个版本共存。
Even deployment of the components of a single BOUNDED CONTEXT can be challenging when data migration takes time or when distributed systems can’t be updated instantaneously, resulting in two versions of the code and data coexisting.
根据部署环境和技术的不同,许多技术因素需要考虑。但有界上下文关系可以帮助您找到关键所在。转换接口已标出。
Many technical considerations come into play depending on the deployment environment and technology. But the BOUNDED CONTEXT relationships can point you toward the hot spots. The translation interfaces have been marked out.
部署方案的可行性应反馈到上下文边界的划分中。当两个上下文通过转换层连接时,只需更新其中一个上下文,即可使新的转换层为另一个上下文提供相同的接口。共享内核会增加协调的负担,不仅在开发阶段,在部署阶段也是如此。采用不同的方式可以大大简化流程。
The feasibility of a deployment plan should feed back into the drawing of the CONTEXT boundaries. When two CONTEXTS are bridged by a translation layer, one CONTEXT can be updated just so a new translation layer provides the same interface to the other CONTEXT. A SHARED KERNEL imposes a much greater burden of coordination, not just in development but also in deployment. SEPARATE WAYS can make life much simpler.
综上所述,统一或整合模型有多种策略。一般来说,您需要在功能无缝集成带来的好处与协调和沟通所需的额外工作量之间进行权衡。您需要在更独立的行动与更顺畅的沟通之间进行权衡。更宏大的统一需要对相关子系统的设计进行控制。
To sum up these guidelines, there is a range of strategies for unifying or integrating models. In general terms, you will trade off the benefits of seamless integration of functionality against the additional effort of coordination and communication. You trade more independent action against smoother communication. More ambitious unification requires control over the design of the subsystems involved.
图 14.13. CONTEXT关系模式的相对需求
Figure 14.13. The relative demands of CONTEXT relationship patterns
很可能,您并非要启动一个新项目,而是希望改进一个正在进行的项目。在这种情况下,第一步是根据现状定义“限界上下文” 。这一点至关重要。为了确保有效性,“上下文图”必须反映团队的真实运作方式,而不是您根据上述指南设想的理想组织架构。
Most likely, you are not starting a project but are looking to improve a project that is already under way. In this case, the first step is to define BOUNDED CONTEXTS according to the way things are now. This is crucial. To be effective, the CONTEXT MAP must reflect the true practice of the teams, not the ideal organization you might decide on by following the guidelines just described.
一旦你明确了当前真正的限界上下文并描述了它们之间的关系,下一步就是围绕当前的组织结构来强化团队的实践。改进上下文内的持续集成。将所有分散的翻译代码重构到反腐层中。为现有的限界上下文命名,并确保它们使用项目的通用语言。
Once you have delineated your true current BOUNDED CONTEXTS and described the relationships they currently have, the next step is to tighten up the team’s practices around that current organization. Improve your CONTINUOUS INTEGRATION within the CONTEXTS. Refactor any stray translation code into your ANTICORRUPTION LAYERS. Name the existing BOUNDED CONTEXTS and make sure they are in the UBIQUITOUS LANGUAGE of the project.
现在,您可以考虑对边界和关系本身进行调整。这些调整自然会遵循我之前为新项目描述的相同原则,但必须分小步进行,务实地选择,以最小的努力和干扰获得最大的价值。
Now you are ready to consider changes to the boundaries and relationships themselves. These changes will naturally be driven by the same principles I’ve already described for a new project, but they will have to be bitten off in small pieces, chosen pragmatically to give the most value for the least effort and disruption.
下一节将讨论一旦你决定更改上下文边界,该如何实际进行更改。
The next section discusses how to go about actually making changes to your CONTEXT boundaries once you have decided to.
与其他建模和设计环节一样,关于限界上下文的决策并非一成不变。不可避免地,您经常需要修改最初关于限界上下文边界及其关系的决策。一般来说,拆分上下文比较容易,但合并上下文或改变上下文之间的关系则颇具挑战性。我将介绍一些既困难又重要的典型变更。这些变更通常过于庞大,无法通过一次重构甚至一次项目迭代完成。因此,我制定了一系列可控步骤的方案,帮助您逐步完成这些变更。当然,这些只是指导原则,您需要根据自身情况和具体事件进行调整。
Like any other aspect of modeling and design, decisions about BOUNDED CONTEXTS are not irrevocable. Inevitably, there will be many cases in which you have to change your initial decision about the boundaries and relationships between BOUNDED CONTEXTS. Generally speaking, breaking up CONTEXTS is pretty easy, but merging them or changing the relationships between them is challenging. I’ll describe a few representative changes that are difficult yet important. These transformations are usually much too big to be taken in a single refactoring or possibly even in a single project iteration. For that reason, I’ve outlined game plans for making these transformations as a series of manageable steps. These are, of course, guidelines that you will have to adapt to your particular circumstances and events.
翻译成本太高。重复内容太明显。合并有界上下文有很多理由。但这很难做到。现在还不算晚,但需要一些耐心。
Translation overhead is too high. Duplication is too obvious. There are many motivations for merging BOUNDED CONTEXTS. This is hard to do. It’s not too late, but it takes some patience.
即使你的最终目标是完全合并到具有持续集成的单一上下文中,也要从迁移到共享内核开始。
Even if your eventual goal is to merge completely to a single CONTEXT with CONTINUOUS INTEGRATION, start by moving to a SHARED KERNEL.
1.评估初始情况。在开始将两个情境相互融合之前,务必确保这两个情境在内部确实是统一的。
1. Evaluate the initial situation. Be sure that the two CONTEXTS are indeed internally unified before beginning to unify them with each other.
2.设置流程。你需要决定代码的共享方式以及模块命名规则。共享内核代码至少每周必须进行一次集成。并且必须配备测试套件。在开发任何共享代码之前,请先完成这些设置。(测试套件将为空,因此应该很容易通过!)
2. Set up the process. You’ll need to decide how the code will be shared and what the module naming conventions will be. There must be at least weekly integration of the SHARED KERNEL code. And it must have a test suite. Set this up before developing any shared code. (The test suite will be empty, so it should be easy to pass!)
3.首先选择一个较小的子域名——在两个上下文中都存在重复,但并非核心域名的一部分。这次合并的目的是建立流程,因此最好选择简单、相对通用或非关键的子域名。检查已有的集成和翻译。选择正在翻译的子域名的好处在于,可以从一个经过验证的翻译入手,而且还能减少翻译工作量。
3. Choose some small subdomain to start with—something duplicated in both CONTEXTS, but not part of the CORE DOMAIN. This first merger is going to establish the process, so it is best to use something simple and relatively generic or noncritical. Examine the integrations and translations that already exist. Choosing something that is being translated has the advantage of starting out with a proven translation, plus you’ll be thinning your translation layer.
此时,您有两个模型都针对同一个子域。合并这两个模型的方法主要有三种。您可以选择其中一个模型,并重构另一个上下文以使其兼容。您可以一次性做出决定,即系统地替换一个上下文中的模型,同时保持作为一个整体开发的模型的连贯性。或者,您可以一次选择一部分进行合并,最终可能得到两者的优点(但要注意避免最终得到一个混乱的模型)。
At this point, you have two models that address the same subdomain. There are basically three approaches to merging. You can choose one model and refactor the other CONTEXT to be compatible. This decision can be made wholesale, setting the intention of systematically replacing one CONTEXT’S model and retaining the coherence of a model that was developed as a unit. Or you can choose one piece at a time, presumably ending up with the best of both (but taking care not to end up with a jumble).
第三种选择是找到一种新的模型,这种模型应该比原有的两个模型都更深入,能够承担起这两个模型的责任。
The third option is to find a new model, presumably deeper than either of the originals, capable of assuming the responsibilities of both.
4.从两个团队中各选出两到四名开发人员,组成一个小组,共同制定子域的共享模型。无论模型是如何构建的,都必须进行细致的完善。这包括识别同义词和映射所有尚未翻译的术语等繁琐工作。这个联合小组还需为模型制定一套基本的测试用例。
4. Form a group of two to four developers, drawn from both teams, to work out a shared model for the subdomain. Regardless of how the model is derived, it must be ironed out in detail. This includes the hard work of identifying synonyms and mapping any terms that are not already being translated. This joint team outlines a basic set of tests for the model.
5.两个团队的开发人员分别负责实现模型(或对现有代码进行调整以共享),完善细节并使其正常运行。如果这些开发人员在使用模型时遇到问题,他们将重新召集步骤 3 中的团队,并参与对概念进行必要的修订。
5. Developers from either team take on the task of implementing the model (or adapting existing code to be shared), working out details and making it function. If these developers run into problems with the model, they reconvene the team from step 3 and participate in any necessary revisions of the concepts.
6.每个团队的开发人员负责与新的共享内核集成。
6. Developers of each team take on the task of integrating with the new SHARED KERNEL.
7. Remove translations that are no longer needed.
此时,您将拥有一个规模很小的共享内核,并已建立相应的维护流程。在后续的项目迭代中,重复步骤 3 到 7 以共享更多内容。随着流程的完善和团队信心的增强,您可以承担更复杂的子域、同时管理多个子域,或者管理核心域中的子域。
At this point, you will have a very small SHARED KERNEL, with a process in place to maintain it. In subsequent project iterations, repeat steps 3 through 7 to share more. As the processes firm up and the teams gain confidence, you can take on more complicated subdomains, multiple ones at the same time, or subdomains that are in the CORE DOMAIN.
注意:随着模型中领域特定部分的增加,您可能会遇到两个模型已遵循不同用户群体的专业术语的情况。除非您已取得突破性进展,开发出一种能够取代这两个专业术语的深度模型,否则明智的做法是暂缓将它们合并到共享内核中。共享内核的优势在于,它既能带来持续集成的部分优势,又能保留独立路径的部分优势。
A note: As you take on more domain-specific parts of the models, you may encounter cases where the two models have conformed to the specialized jargon of different user communities. It is wise to defer merging these into the SHARED KERNEL unless a breakthrough to a deep model has occurred, providing you with a language capable of superseding both specialized ones. An advantage of a SHARED KERNEL is that you can have some of the advantages of CONTINUOUS INTEGRATION while retaining some of the advantages of SEPARATE WAYS.
以上是一些关于合并到共享内核的指导原则。在继续之前,请考虑一种能够满足本次转换部分需求的替代方案。如果两种模型中有一种是更优的,可以考虑直接迁移到该模型,而无需进行集成。与其共享公共子域,不如通过重构应用程序,使其调用更受青睐的边界上下文的模型,并对该模型进行必要的增强,从而将这些子域的全部责任从一个边界上下文转移到另一个边界上下文。这样,无需任何持续的集成开销,即可消除冗余。理论上(但并非必然),更受青睐的边界上下文最终可能会完全接管,从而达到与合并相同的效果。在过渡过程中(过渡过程可能相当漫长或不确定),这种方式会像走分离路线一样,存在一些常见的优缺点,您需要权衡这些优缺点与共享内核的利弊。
Those are some guidelines for merging into a SHARED KERNEL. Before going ahead, consider one alternative that satisfies some of the needs addressed by this transformation. If one of the two models is definitely preferred, consider shifting toward it without integrating. Instead of sharing common subdomains, just systematically transfer full responsibility for those subdomains from one BOUNDED CONTEXT to the other by refactoring the applications to call on the model of the more favored CONTEXT, and making any enhancements that model needs. Without any ongoing integration overhead, you have eliminated redundancy. Potentially (but not necessarily), the more favored BOUNDED CONTEXT could eventually take over completely, and you’ll have created the same effect as a merger. In the transition (which can be quite long or indefinite), this will have the usual advantages and disadvantages of going SEPARATE WAYS, and you have to weigh them against the pros and cons of a SHARED KERNEL.
如果你的共享核心正在扩展,你可能会被两个有界上下文完全统一的优势所吸引。这不仅仅是解决模型差异的问题。你将改变团队结构,最终还会改变人们使用的语言。
If your SHARED KERNEL is expanding, you may be lured by the advantages of full unification of the two BOUNDED CONTEXTS. This is not just a matter of resolving the model differences. You are going to be changing team structures and ultimately the language people speak.
Start by preparing the people and the teams.
1.确保每个团队都分别落实持续集成所需的所有流程(共享代码所有权、频繁集成等)。协调两个团队的集成流程,使每个人都以相同的方式行事。
1. Be sure that all the processes needed for CONTINUOUS INTEGRATION (shared code ownership, frequent integration, and so on) are in place on each team, separately. Harmonize integration procedures on the two teams so that everyone is doing things in the same way.
2.开始在两个团队之间轮换团队成员。这将建立一个了解两种模型的人才库,并开始将两个团队的成员联系起来。
2. Start circulating team members between teams. This will create a pool of people who understand both models, and will begin to connect the people of the two teams.
3.分别阐明每个模型的蒸馏过程。(参见第15章。)
3. Clarify the distillation of each model individually. (See Chapter 15.)
4.此时,信心应该足够高,可以开始将核心域合并到共享内核中。这可能需要多次迭代,有时需要在新共享的部分和尚未共享的部分之间使用临时转换层。一旦开始合并核心域,最好加快速度。这是一个高开销、容易出错的阶段,应该尽可能缩短,并优先于大多数新开发工作。但切勿承担超出自身能力范围的任务。
4. At this point, confidence should be high enough to begin merging the core domain into the SHARED KERNEL. This can take several iterations, and sometimes temporary translation layers are needed between the newly shared parts and the not-yet-shared parts. Once into merging the CORE DOMAIN, it is best to go pretty fast. It is a high-overhead phase, fraught with errors, and should be shortened as much as possible, taking priority over most new development. But don’t take on more than you can handle.
要合并核心模型,您有几种选择。您可以保留一个模型,并修改另一个模型使其与之兼容;或者,您可以创建一个新的子域模型,并使两个上下文都使用该模型。需要注意的是,这两个模型是否针对不同的用户需求而设计。您可能需要两个原始模型各自的特殊功能。这就需要开发一个更深层次的模型来取代这两个原始模型。开发一个更深层次的统一模型非常困难,但如果您致力于完全合并两个上下文,则无法再使用多种不同的方法。这样做的好处在于,最终的模型和代码集成将更加清晰。但请注意,不要因此而牺牲满足用户特定需求的能力。
To merge the CORE models, you have a few choices. You can stick with one model and modify the other to be compatible with it, or you can create a new model of the subdomain and adapt both contexts to use it. Watch out if the two models have been tailored to address distinct user needs. You may need the specialized power of both original models. This calls for developing a deeper model that can supersede both original models. Developing a deeper unifying model is very difficult, but if you are committed to the full merger of the two CONTEXTS, you no longer have the option of multiple dialects. There will be a reward in terms of the clarity of integration of the resulting model and code. Be careful that it doesn’t come at the cost of your ability to address the specialized needs of your users.
5.随着共享内核的增长,将集成频率提高到每天,最终提高到持续集成。
5. As the SHARED KERNEL grows, increase the integration frequency to daily and finally to CONTINUOUS INTEGRATION.
6. 当共享内核接近涵盖所有两个先前的有界上下文时,你会发现自己要么有一个大团队,要么有两个小团队,他们拥有一个共享的代码库,他们不断集成,并且经常来回交换成员。
6. As the SHARED KERNEL approaches the point of encompassing all of the two former BOUNDED CONTEXTS, you will find yourself with either one large team or two smaller teams that have a shared code base that they INTEGRATE CONTINUOUSLY, and that trade members back and forth frequently.
世间万物终有尽头,就连老旧的计算机软件也不例外。但这并非自然而然发生的。这些旧系统往往与企业和其他系统紧密交织,剥离它们可能需要数年时间。幸运的是,不必一次性全部完成。
All good things must come to an end, even legacy computer software. But it doesn’t happen on its own. These old systems can be so woven into the business and other systems that extricating them can take many years. Fortunately, it doesn’t have to be done all at once.
可能性太多,我在这里只能略作探讨。但我会讨论一个常见的例子:公司日常使用的一个旧系统最近被几个更现代的系统所补充,这些系统通过一个反腐败层与旧系统通信。
The possibilities are too various for me to do more than scratch the surface here. But I’ll discuss a common case: An old system that is used daily in the business has been supplemented recently by a handful of more modern systems that communicate with the legacy system through an ANTICORRUPTION LAYER.
首要步骤之一是制定测试策略。新系统中的新功能需要编写自动化单元测试,但逐步淘汰旧系统会带来特殊的测试需求。一些组织会在一段时间内并行运行新旧系统。
One of the first steps should be to decide on a testing strategy. Automated unit tests should be written for new functionality in the new systems, but phasing out legacy introduces special testing needs. Some organizations run new and old in parallel for some period of time.
在任何一次迭代中:
In any given iteration:
1.确定可以在一次迭代中添加到首选系统中的遗留系统的具体功能。
1. Identify specific functionality of the legacy that could be added to one of the favored systems within a single iteration.
2.确定反腐败层需要增加的内容。
2. Identify additions that will be required in the ANTICORRUPTION LAYER.
3.执行。
3. Implement.
4.部署。
4. Deploy.
有时需要花费多个迭代周期来编写与可以逐步淘汰的旧单元等效的功能,但仍然要以小的、迭代规模的单元来规划新功能,只需等待多个迭代周期即可部署。
Sometimes it will be necessary to spend more than one iteration writing equivalent functionality to a unit that can be phased out of the legacy, but still plan the new functions in small, iteration-sized units, only waiting multiple iterations for deployment.
部署是另一个存在太多变数而无法面面俱到的环节。如果这些小的、增量式的变更能够直接部署到生产环境,对开发来说当然很好,但通常情况下,我们需要组织更大规模的版本发布。用户必须接受培训。使用新软件。有时必须顺利完成并行开发阶段。许多后勤问题需要解决。
Deployment is another point at which too much variation exists to cover all the bases. It would be nice for development if these small, incremental changes could be rolled out to production, but usually it is necessary to organize bigger releases. The users must be trained to use the new software. A parallel period sometimes must be completed successfully. Many logistical problems will have to be worked out.
一旦它最终在现场运行起来:
Once it is finally running in the field:
5.找出反腐败层中任何不必要的部分并将其删除。
5. Identify any unnecessary parts of the ANTICORRUPTION LAYER and remove them.
6.考虑移除旧系统中目前未使用的模块,但这可能并不实际。讽刺的是,旧系统设计得越好,就越容易逐步淘汰。但设计糟糕的软件很难一点一点地拆解。或许可以先忽略未使用的部分,等到其他部分也逐步淘汰后再处理,届时整个系统就可以关闭了。
6. Consider excising the now-unused modules of the legacy system, though this may not turn out to be practical. Ironically, the better designed the legacy system is, the easier it will be to phase it out. But badly designed software is hard to dismantle a little at a time. It may be possible to just ignore the unused parts until a later time when the remainder has been phased out and the whole thing can be switched off.
反复重复这个过程。旧系统对业务的影响会逐渐减弱,最终就能看到曙光,彻底关闭旧系统。与此同时,随着系统间相互依赖性的增减,反腐败层也会随之增减。当然,在其他条件相同的情况下,你应该优先迁移那些能缩小反腐败层规模的功能。但其他因素可能起主导作用,因此在某些过渡阶段,你可能不得不面对一些棘手的转换问题。
Repeat this over and over. The legacy system should become less involved in the business, and eventually it will be possible to see the light at the end of the tunnel and finally switch off the old system. Meanwhile, the ANTICORRUPTION LAYER will alternately shrink and swell as various combinations increase or decrease the interdependence between the systems. All else being equal, of course, you should migrate first those functions that lead to smaller ANTICORRUPTION LAYERS. But other factors are likely to dominate, and you may have to live with some hairy translations during some transitions.
您一直使用一系列临时协议与其他系统集成,但随着越来越多的系统需要访问权限,维护负担日益加重,或者交互变得难以理解。您需要使用公开语言来规范系统之间的关系。
You have been integrating with other systems with a series of ad hoc protocols, but the maintenance burden is mounting as more systems want access, or perhaps the interaction is becoming very difficult to understand. You need to formalize the relationship between the systems with a PUBLISHED LANGUAGE.
1.如果有行业标准语言可用,请对其进行评估,并尽可能使用它。
1. If an industry-standard language is available, evaluate it and use it if at all possible.
2.如果没有标准语言或预先发布的语言可用,则首先要完善将作为宿主系统的CORE DOMAIN 。(参见第15章。)
2. If no standard or prepublished language is available, then begin by sharpening up the CORE DOMAIN of the system that will serve as the host. (See Chapter 15.)
3.使用核心域 尽可能使用 XML 等标准交换范式作为交换语言的基础。
3. Use the CORE DOMAIN as the basis of an interchange language, using a standard interchange paradigm such as XML, if at all possible.
4.将新语言发布给所有参与合作的人(至少)。
4. Publish the new language to all involved in the collaboration (at least).
5.如果涉及新的系统架构,也应该公布出来。
5. If a new system architecture is involved, publish that too.
6.为每个协作系统构建翻译层。
6. Build translation layers for each collaborating system.
7.切换。
7. Switch over.
此时,其他合作者应该能够以最小的干扰加入。
At this point, additional collaborators should be able to enter with minimal disruption.
请记住,已发布的语言必须稳定,但您仍然需要保留在持续重构过程中更改宿主模型的自由。因此,不要将交换语言与宿主模型等同起来。保持它们接近可以减少翻译开销,您可以选择让宿主遵循统一的语言。但如果成本效益权衡利弊后,您仍应保留增强翻译层并偏离现有语言的权利。
Remember, the PUBLISHED LANGUAGE must be stable, yet you’ll still need the freedom to change the host’s model as you continue your relentless refactoring. Therefore, do not equate the interchange language and the model of the host. Keeping them close together will reduce translation overhead, and you may choose to make your host a CONFORMIST. But reserve the right to beef up the translation layer and diverge if the cost-benefit trade-off favors that.
项目负责人应根据功能集成需求和开发团队之间的关系来定义限界上下文。一旦明确定义并遵守限界上下文和上下文映射,逻辑一致性就能得到保障。相关的沟通问题至少会被暴露出来,以便及时解决。
Project leaders should define BOUNDED CONTEXTS based on functional integration requirements and relationships of development teams. Once BOUNDED CONTEXTS and a CONTEXT MAP are explicitly defined and respected, then logical consistency should be protected. Related communication problems will at least be exposed so they can be dealt with.
然而,有时模型情境(无论是有意设定的还是自然形成的)会被误用,用于解决系统内部逻辑不一致以外的问题。团队可能会发现,大型情境的模型过于复杂,难以整体理解或全面分析。无论是出于选择还是偶然,这通常会导致将情境拆分成更易于管理的部分。这种拆分会导致机会的错失。因此,有必要仔细审视在广泛情境中建立大型模型的决策。如果从组织或政治角度来看,无法维持模型的整体性,如果实际上模型正在瓦解,那么就应该重新规划。设定你能遵守的边界。但如果一个大型的有界上下文能够满足迫切的集成需求,并且撇开模型本身的复杂性不谈,它本身似乎可行,那么拆分上下文可能并非最佳方案。
However, sometimes model contexts, whether consciously bounded or naturally occurring, are misapplied to solve problems other than logical inconsistency within a system. The team may find that the model of a large CONTEXT seems too complex to comprehend as a whole, or to analyze completely. By choice or by chance, this often leads to breaking down the CONTEXTS into more manageable pieces. This fragmentation leads to lost opportunities. Now, it is worth scrutinizing a decision to establish a large model in a broad CONTEXT, and if it is not organizationally or politically possible to keep together, if it is in reality fragmenting, then redraw the map and define boundaries you can keep. But if a large BOUNDED CONTEXT addresses compelling integration needs, and if it seems feasible apart from the complexity of the model itself, then breaking up the CONTEXT may not be the best answer.
在做出这种牺牲之前,应该考虑其他使大型模型易于处理的方法。接下来的两章将重点介绍如何通过应用两个更广泛的原则——提炼和大规模结构——来管理大型模型中的复杂性。
There are other means of making large models tractable that should be considered before making this sacrifice. The next two chapters focus on managing complexity within a big model by applying two more broad principles: distillation and large-scale structure.
——詹姆斯·克拉克·麦克斯韦,《电磁学专著》,1873年
—James Clerk Maxwell, A Treatise on Electricity and Magnetism, 1873
这四个方程式,连同其中术语的定义以及它们所依据的数学体系,表达了十九世纪古典电磁学的全部内容。
These four equations, along with the definitions of their terms and the body of mathematics they rest on, express the entirety of classical nineteenth-century electromagnetism.
如何专注于核心问题,避免被各种次要问题淹没?分层架构将领域概念与计算机系统运行的技术逻辑分离,但在大型系统中,即使是隔离的领域也可能复杂到难以管理。
How do you focus on your central problem and keep from drowning in a sea of side issues? A LAYERED ARCHITECTURE separates domain concepts from the technical logic that makes a computer system run, but in a large system, even the isolated domain may be unmanageably complex.
蒸馏是将混合物中的成分分离,提取其精华,使其更有价值和用途的过程。模型是对知识的提炼。每次重构以获得更深入的理解时,我们都会抽象出领域知识和优先级的一些关键方面。现在,让我们从战略角度出发,本章将探讨如何区分模型的各个重要部分,并对整个领域模型进行提炼。
Distillation is the process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. A model is a distillation of knowledge. With every refactoring to deeper insight, we abstract some crucial aspect of domain knowledge and priorities. Now, stepping back for a strategic view, this chapter looks at ways to distinguish broad swaths of the model and distill the domain model as a whole.
与许多化学蒸馏一样,分离出的副产品本身也因蒸馏过程而变得更有价值(如通用子域和相干机制),但这种努力其动机在于提取出一个特别有价值的部分,即使我们的软件与众不同并使其值得构建的部分:“核心领域”。
As with many chemical distillations, the separated by-products are themselves made more valuable by the distillation process (as GENERIC SUBDOMAINS and COHERENT MECHANISMS), but the effort is motivated by the desire to extract that one particularly valuable part, the part that distinguishes our software and makes it worth building: the “CORE DOMAIN.”
领域模型的战略提炼包含以下所有步骤:
Strategic distillation of a domain model does all of the following:
1.帮助所有团队成员理解系统的整体设计及其各部分之间的相互关系。
1. Aids all team members in grasping the overall design of the system and how it fits together
2.通过确定一个易于管理的核心模型来促进沟通,从而进入普适语言领域
2. Facilitates communication by identifying a core model of manageable size to enter the UBIQUITOUS LANGUAGE
3.指导重构
3. Guides refactoring
4.将工作重点放在模型中最有价值的部分。
4. Focuses work on areas of the model with the most value
5.指导外包、现成组件的使用以及任务分配决策
5. Guides outsourcing, use of off-the-shelf components, and decisions about assignments
本章阐述了对核心领域进行战略提炼的系统方法,并解释了如何在团队内部有效地分享对核心领域的看法,并提供了讨论我们正在做的事情的语言。
This chapter lays out a systematic approach to strategic distillation of the CORE DOMAIN, and it explains how to effectively share a view of it within the team and provides the language to talk about what we are doing.
图 15.1. 战略提炼导航图
Figure 15.1. A navigation map for strategic distillation
就像园丁修剪树木,为主要枝干的生长腾出空间一样,我们将运用一系列技巧来剔除模型中的干扰因素,并将注意力集中在最重要的部分……
Like a gardener pruning a tree, clearing the way for the growth of the main branches, we are going to apply a suite of techniques to hew away distractions in the model and focus our attention on the part that matters most. . . .
在设计一个大型系统时,有许多组成组件,每个组件都很复杂,而且对于成功来说都绝对必要,因此领域模型的本质(真正的业务资产)可能会被掩盖和忽视。
In designing a large system, there are so many contributing components, all complicated and all absolutely necessary to success, that the essence of the domain model, the real business asset, can be obscured and neglected.
一个难以理解的系统难以改变。改变的影响也难以预见。开发人员一旦涉足自己不熟悉的领域,就会迷失方向。(这一点在团队引入新成员时尤为明显,但即使是经验丰富的团队成员,如果代码不够清晰易懂、结构严谨,也会感到吃力。)这迫使人们进行专业化分工。当开发人员将工作局限于特定模块时,知识转移会进一步减少。工作被分割开来,系统集成不顺畅,分配工作的灵活性也随之丧失。当开发人员没有意识到某种行为已存在于其他地方时,就会出现重复工作,从而使系统变得更加复杂。
A system that is hard to understand is hard to change. The effect of a change is hard to foresee. A developer who wanders outside his or her own area of familiarity gets lost. (This is particularly true when bringing new people into a team, but even an established member of the team will struggle unless code is very expressive and organized.) This forces people to specialize. When developers confine their work to specific modules, it further reduces knowledge transfer. With the compartmentalization of work, smooth integration of the system suffers, and flexibility in assigning work is lost. Duplication crops up when a developer does not realize that a behavior already exists elsewhere, and so the system becomes even more complex.
这些都是难以理解的设计所带来的一些后果,但失去对领域全局的把握还会带来另一个同样严重的风险:
Those are some of the consequences of any design that is hard to understand, but there is another, equally serious risk from losing the big picture of the domain:
残酷的现实是,设计的各个部分不可能都同样精益求精。必须设定优先级。为了使领域模型成为宝贵的资产,模型的核心部分必须简洁高效,并能充分利用其功能来创建应用程序。然而,稀缺的高技能开发人员往往更倾向于技术基础设施或定义清晰、无需专业知识即可理解的领域问题。
The harsh reality is that not all parts of the design are going to be equally refined. Priorities must be set. To make the domain model an asset, the model’s critical core has to be sleek and fully leveraged to create application functionality. But scarce, highly skilled developers tend to gravitate to technical infrastructure or neatly definable domain problems that can be understood without specialized domain knowledge.
系统的这些部分对计算机科学家来说似乎很有意思,他们认为这些工作能够培养可迁移的专业技能,并为简历增添亮点。然而,真正使应用程序脱颖而出并使其成为商业资产的核心部分,通常最终却是由技能较低的开发人员与数据库管理员合作完成的。他们先创建数据模式,然后逐个功能地编写代码,完全没有运用模型中的任何概念性知识。
Such parts of the system seem interesting to computer scientists, and are perceived to build transferable professional skills and provide better resume material. The specialized core, that part of the model that really differentiates the application and makes it a business asset, typically ends up being put together by less skilled developers who work with DBAs to create a data schema and then code feature-by-feature without drawing on any conceptual power in the model at all.
软件这部分的设计或实现不佳会导致应用程序无法为用户提供任何有价值的功能,无论技术基础架构多么完善,辅助功能多么出色。当项目缺乏对整体设计以及各个部分相对重要性的清晰认识时,这种隐患就可能滋生。
Poor design or implementation of this part of the software leads to an application that never does compelling things for the users, no matter how well the technical infrastructure works, no matter how nice the supporting features are. This insidious problem can take root when a project lacks a sharp picture of the overall design and the relative significance of the various parts.
我参与过的最成功的项目之一,最初也深受这种弊病的困扰。该项目的目标是开发一个非常复杂的银团贷款系统。大部分优秀人才都乐于钻研数据库映射层和消息传递接口,而业务模型却掌握在对面向对象技术一窍不通的开发人员手中。
One of the most successful projects I’ve joined initially suffered from this syndrome. The goal was to develop a very complex syndicated loan system. Most of the strong talent was happily working on database mapping layers and messaging interfaces while the business model was in the hands of developers new to object technology.
唯一的例外是一位经验丰富的对象开发人员,他正在研究一个领域问题,并设计了一种方法,可以将注释附加到任何长期存在的领域对象上。这些注释可以进行组织,以便交易员能够查看他们或其他人记录的过去决策的理由。他还构建了一个简洁的用户界面,使用户能够直观地访问注释模型的灵活功能。
The single exception, an experienced object developer working on a domain problem, devised a way of attaching comments to any of the long-lived domain objects. These comments could be organized so that traders could see the rationale they or others recorded for some past decision. He also built an elegant user interface that gave intuitive access to the flexible features of the comment model.
这些功能实用且设计精良,已投入生产。
These features were useful and well designed. They went into production.
可惜,他们只是配角。这位才华横溢的开发者设计了一种有趣且通用的注释方式,并将其清晰地实现,交付给了用户。与此同时,一位不称职的开发者却把至关重要的“贷款”模块搞得一团糟,项目几乎因此而无法挽回。
Unfortunately, they were peripheral. This talented developer modeled his interesting, generic way of commenting, implemented it cleanly, and put it into users’ hands. Meanwhile an incompetent developer was turning the mission-critical “loan” module into an incomprehensible tangle that the project very nearly did not recover from.
规划过程必须将资源集中到模型和设计中最关键的环节。为此,在规划和开发过程中,所有相关人员都必须识别并理解这些关键点。
The planning process must drive resources to the most crucial points in the model and design. To do that, those points must be identified and understood by everyone during planning and development.
模型中那些独特且对预期应用至关重要的部分构成了核心域。核心域是系统中应该增加最大价值的地方。
Those parts of the model distinctive and central to the purposes of the intended applications make up the CORE DOMAIN. The CORE DOMAIN is where the most value should be added in your system.
所以:
Therefore:
将模型精简。找到核心领域,并提供一种能够轻松将其与大量辅助模型和代码区分开来的方法。突出最有价值和最专业的概念。精简核心。
Boil the model down. Find the CORE DOMAIN and provide a means of easily distinguishing it from the mass of supporting model and code. Bring the most valuable and specialized concepts into sharp relief. Make the CORE small.
将顶尖人才投入核心领域,并据此进行招聘。将精力集中在核心领域,以找到一个深层次的模型并开发一个灵活的设计——足以实现系统的愿景。任何其他部分的投入都必须以其如何支持精炼的核心部分为依据。
Apply top talent to the CORE DOMAIN, and recruit accordingly. Spend the effort in the CORE to find a deep model and develop a supple design—sufficient to fulfill the vision of the system. Justify investment in any other part by how it supports the distilled CORE.
提炼核心领域并非易事,但它确实能带来一些简单的决策。你需要投入大量精力来使核心功能独树一帜,同时尽可能保持设计其他部分的通用性。如果你需要将设计的某些方面保密以保持竞争优势,那么这部分就是核心领域。其他部分则无需费力隐藏。此外,当需要在两个可行的重构方案之间做出选择时(例如由于时间限制),应优先选择对核心领域影响最大的方案。
Distilling the CORE DOMAIN is not easy, but it does lead to some easy decisions. You’ll put a lot of effort into making your CORE distinctive, while keeping the rest of the design as generic as is practical. If you need to keep some aspect of your design secret as a competitive advantage, it is the CORE DOMAIN. There is no need to waste effort concealing the rest. And whenever a choice has to be made (due to time limitations) between two desirable refactorings, the one that most affects the CORE DOMAIN should be chosen first.
本章中的模式使核心领域更容易被看到、使用和改变。
The patterns in this chapter make the CORE DOMAIN easier to see and use and change.
我们正在研究模型中那些与表示您的业务领域和解决您的业务问题相关的部分。
We are looking at those parts of the model particular to representing your business domain and solving your business problems.
您选择的核心领域取决于您的视角。例如,许多应用程序需要一个通用的货币模型来表示各种货币及其汇率和转换。另一方面,支持货币交易的应用程序可能需要更复杂的货币模型,这部分模型将被视为核心领域的一部分。即使在这种情况下,货币模型中也可能存在非常通用的部分。随着经验的积累,对该领域的理解不断加深,可以通过分离通用货币概念,并将模型中专门的方面保留在核心领域,从而继续提炼过程。
The CORE DOMAIN you choose depends on your point of view. For example, many applications need a generic model of money that could represent various currencies and their exchange rates and conversions. On the other hand, an application to support currency trading might need a more elaborate model of money, which would be considered part of the CORE. Even in such a case, there may be a part of the money model that is very generic. As insight into the domain deepens with experience, the distillation process can continue by separating the generic money concepts and retaining only the specialized aspects of the model in the CORE DOMAIN.
在航运应用中,核心模型可以是货物如何拼装运输、集装箱交接时责任如何转移,或者特定集装箱如何运作的模型。经由多种运输方式抵达目的地。在投资银行业,核心模型可能包括受让人和参与者之间资产银团模式。
In a shipping application, the CORE could be the model of how cargoes are consolidated for shipping, how liability is transferred when containers change hands, or how a particular container is routed on various transports to reach its destination. In investment banking, the CORE could include the models of syndication of assets among assignees and participants.
一个应用程序的核心域可能是另一个应用程序的通用支持组件。然而,在同一个项目乃至整个公司范围内,通常可以定义一个一致的核心域。与设计的其他部分一样,核心域的识别也需要通过迭代不断完善。某些特定关系的重要性可能一开始并不明显。那些起初看似至关重要的对象,最终可能只是起到辅助作用。
One application’s CORE DOMAIN is another application’s generic supporting component. Still, throughout one project, and usually throughout one company, a consistent CORE can be defined. Like every other part of the design, the identification of the CORE DOMAIN should evolve through iterations. The importance of a particular set of relationships might not be apparent at first. The objects that seem obviously central at first may turn out to have supporting roles.
以下各节的讨论,特别是通用子域部分,将为这些决定提供更多指导。
The discussion in the following sections, particularly GENERIC SUBDOMAINS, will give more guidelines for these decisions.
项目团队中技术最精湛的成员往往缺乏领域知识。这限制了他们的作用,并加剧了将他们分配到辅助性工作的倾向,从而形成恶性循环:知识匮乏使他们无法参与到能够积累领域知识的工作中。
The most technically proficient members of project teams seldom have much knowledge of the domain. This limits their usefulness and reinforces the tendency to assign them to supporting components, sustaining a vicious circle in which lack of knowledge keeps them away from the work that would build domain knowledge.
打破这种恶性循环的关键在于组建一支团队,该团队应由一批实力雄厚、具有长期投入且渴望成为领域知识宝库的开发人员,以及一位或多位对业务有着深刻理解的领域专家组成。认真对待领域设计是一项既有趣又极具技术挑战性的工作,而我们也能找到这样认为的开发人员。
It is essential to break this cycle by assembling a team matching up a set of strong developers who have a long-term commitment and an interest in becoming repositories of domain knowledge with one or more domain experts who know the business deeply. Domain design is interesting, technically challenging work when approached seriously, and developers can be found who see it this way.
通常来说,聘请短期外部设计专家来负责核心领域的具体构建工作并不实际,因为团队需要积累领域知识,而临时成员会造成资源流失。另一方面,专家如果能扮演教学/指导的角色,则能发挥非常大的作用,帮助团队提升领域设计技能,并促进团队成员运用他们可能尚未掌握的复杂原理。
It is usually not practical to hire short-term, outside design expertise for the nuts and bolts of creating the CORE DOMAIN, because the team needs to accumulate domain knowledge, and a temporary member is a leak in the bucket. On the other hand, an expert in a teaching/mentoring role can be very valuable by helping the team build its domain design skills and facilitating the use of sophisticated principles that team members probably have not mastered.
出于类似的原因,核心领域不太可能被收购。目前已有一些针对特定行业的模型框架开发工作,其中较为突出的例子包括半导体行业联盟SEMATECH面向半导体制造自动化开发的CIM框架,以及IBM面向众多企业的“旧金山”框架。尽管这是一个非常诱人的想法,迄今为止,除了作为已发布的语言促进数据交换之外(参见第14章) ,其成果并不令人信服。《领域特定应用框架》(Fayad 和 Johnson,2000 )一书概述了该领域的最新进展。随着该领域的发展,未来可能会出现更多可行的框架。
For similar reasons, it is unlikely that the CORE DOMAIN can be purchased. Efforts have been made to build industry-specific model frameworks, conspicuous examples being the semiconductor industry consortium SEMATECH’s CIM framework for semiconductor manufacturing automation, and IBM’s “San Francisco” frameworks for a wide range of businesses. Although this is a very enticing idea, so far the results have not been compelling, except perhaps as PUBLISHED LANGUAGES facilitating data interchange (see Chapter 14). The book Domain-Specific Application Frameworks (Fayad and Johnson 2000) gives an overview of the state of this art. As the field advances, more workable frameworks may be available.
即便如此,谨慎行事还有更根本的原因:定制软件的最大价值在于对核心领域的完全掌控。一个设计良好的框架可以提供高级抽象,您可以根据自身需求进行定制。它可以让您免于开发通用部分,从而专注于核心功能。但如果它对您的限制过多,则可能存在以下三种情况。
Even so, there is a more fundamental reason for caution: The greatest value of custom software comes from the total control of the CORE DOMAIN. A well-designed framework may be able to provide high-level abstractions that you can specialize for your use. It may save you from developing the more generic parts and leave you free to concentrate on the CORE. But if it constrains you more than that, then there are three likely possibilities.
1.你正在失去一项重要的软件资产。在你的核心领域中,请放弃那些限制性的框架。
1. You are losing an essential software asset. Back off restrictive frameworks in your CORE DOMAIN.
2.该框架所涵盖的区域并不像你想象的那样关键。重新划定核心领域的边界,使其与模型中真正独特的部分相符。
2. The area treated by the framework is not as pivotal as you thought. Redraw the boundaries of the CORE DOMAIN to the truly distinctive part of the model.
3.您的核心领域没有特殊需求。可以考虑风险较低的解决方案,例如购买软件与您的应用程序集成。
3. You don’t have special needs in your CORE DOMAIN. Consider a lower-risk solution, such as purchasing software to integrate with your applications.
无论如何,打造独具特色的软件最终都离不开一个稳定的团队积累专业知识,并将其整合到一个完善的模型中。没有捷径,也没有灵丹妙药。
One way or another, creating distinctive software comes back to a stable team accumulating specialized knowledge and crunching it into a rich model. No shortcuts. No magic bullets.
本章其余部分介绍的各种蒸馏技术几乎可以按任何顺序应用,但它们对设计的改变程度却有所不同。
The various distillation techniques that make up the rest of this chapter can be applied in almost any order, but there is a range in how radically they modify the design.
简洁的领域愿景声明能够以最小的投入传达基本概念及其价值。重点突出的核心内容可以改善沟通,指导决策,而且几乎不需要对设计进行任何修改。
A simple DOMAIN VISION STATEMENT communicates the basic concepts and their value with a minimum investment. The HIGHLIGHTED CORE can improve communication and help guide decision making—and still requires little or no modification to the design.
更积极的重构和重新打包明确地分离了通用子域,以便可以单独处理它们。内聚机制可以用灵活、易于沟通的封装方式进行封装。以及灵活的设计。去除这些干扰因素,就能理清核心。
More aggressive refactoring and repackaging explicitly separate GENERIC SUBDOMAINS, which can then be dealt with individually. COHESIVE MECHANISMS can be encapsulated with versatile, communicative, and supple design. Removing these distractions disentangles the CORE.
重新打包分离的核心,使得核心可以直接在代码中显示,并有助于未来对核心模型进行研究。
Repackaging a SEGREGATED CORE makes the CORE directly visible, even in the code, and facilitates future work on the CORE model.
其中最具雄心的是抽象核心,它以纯粹的形式表达了最基本的概念和关系(并且需要对模型进行广泛的重组和重构)。
And most ambitious is the ABSTRACT CORE, which expresses the most fundamental concepts and relationships in a pure form (and requires extensive reorganizing and refactoring of the model).
每一种技术都需要投入越来越多的精力,但正如刀刃越磨越锋利一样,领域模型的不断提炼最终会形成一种资产,赋予项目速度、敏捷性和执行精度。
Each of these techniques requires a successively greater commitment, but a knife gets sharper as its blade is ground finer. Successive distillation of a domain model produces an asset that gives the project speed, agility, and precision of execution.
首先,我们可以剔除模型中最不显著的方面。通用子域与核心域形成对比,从而阐明每个子域的含义……
To start, we can boil off the least distinctive aspects of the model. GENERIC SUBDOMAINS provide a contrast to the CORE DOMAIN that clarifies the meaning of each. . . .
模型中的某些部分增加了复杂性,却未能捕捉或传达专业知识。任何无关的内容都会使核心领域更难辨识和理解。模型充斥着人尽皆知的通用原则,或是属于非核心专业领域但起到辅助作用的细节。然而,无论这些其他元素多么通用,它们对于系统的运行和模型的完整表达都至关重要。
Some parts of the model add complexity without capturing or communicating specialized knowledge. Anything extraneous makes the CORE DOMAIN harder to discern and understand. The model clogs up with general principles everyone knows or details that belong to specialties which are not your primary focus but play a supporting role. Yet, however generic, these other elements are essential to the functioning of the system and the full expression of the model.
你的模型中有一部分你可能想当然地认为它理所当然。它无疑是领域模型的一部分,但它抽象出的概念对很多企业来说可能都至关重要。例如,航运、银行或制造业等各种类型的企业都需要某种形式的组织结构图。再比如,许多应用程序会跟踪应收账款、费用账簿和其他财务事项,而这些都可以使用通用会计模型来处理。
There is a part of your model that you would like to take for granted. It is undeniably part of the domain model, but it abstracts concepts that would probably be needed for a great many businesses. For example, a corporate organization chart is needed in some form by businesses as diverse as shipping, banking, or manufacturing. For another example, many applications track receivables, expense ledgers, and other financial matters that could all be handled using a generic accounting model.
人们常常会在一些无关紧要的细节问题上耗费大量精力。我亲眼目睹过两个项目,他们耗费数周时间,让最优秀的开发人员重新设计包含时区的日期和时间。虽然这些组件必须能够正常运行,但它们并非系统的核心概念。
Often a great deal of effort is spent on peripheral issues in the domain. I personally have witnessed two separate projects that have employed their best developers for weeks in redesigning dates and times with time zones. While such components must work, they are not the conceptual core of the system.
即使这种通用模型元素被认为至关重要,整体领域模型也需要突出系统中最具价值和最特殊的方面,并且需要精心构建,以赋予这些部分尽可能大的权力。当核心部分与所有相互关联的因素混杂在一起时,这一点就很难做到。
Even if such a generic model element is deemed critical, the overall domain model needs to make prominent the most value-adding and special aspects of your system, and needs to be structured to give that part as much power as possible. This is hard to do when the CORE is mixed with all the interrelated factors.
所以:
Therefore:
找出与项目动机无关的、具有内在联系的子领域。提取这些子领域的通用模型,并将它们放入单独的模块中。确保这些模块中不留下任何你专业领域的痕迹。
Identify cohesive subdomains that are not the motivation for your project. Factor out generic models of these subdomains and place them in separate MODULES. Leave no trace of your specialties in them.
一旦这些子领域被分离出来,其后续开发的优先级应低于核心领域,并且避免将核心开发人员分配到这些任务中(因为他们从中获得的领域知识有限)。此外,还可以考虑为这些通用子领域采用现成的解决方案或已发布的模型。
Once they have been separated, give their continuing development lower priority than the CORE DOMAIN, and avoid assigning your core developers to the tasks (because they will gain little domain knowledge from them). Also consider off-the-shelf solutions or published models for these GENERIC SUBDOMAINS.
You may have a few extra options when developing these packages.
有时你可以购买现成的实现方案或使用开源代码。
Sometimes you can buy an implementation or use open source code.
优势
Advantages
• 需要开发的代码量更少。
• Less code to develop.
• 将维护负担外包。
• Maintenance burden externalized.
• 代码可能更成熟,在多个地方使用,因此比自写代码更可靠、更完整。
• Code is probably more mature, used in multiple places, and therefore more bulletproof and complete than homegrown code.
缺点
Disadvantages
• 在使用之前,您仍然需要花时间对其进行评估和理解。
• You still have to spend the time to evaluate it and understand it before using it.
• 鉴于我们行业的质量控制现状,你不能指望它是正确和稳定的。
• Quality control being what it is in our industry, you can’t count on it being correct and stable.
• 对于您的目的而言,它可能过于复杂;集成可能比自行开发的极简方案更费力。
• It may be overengineered for your purposes; integration could be more work than a minimalist homegrown implementation.
• 外来元素通常无法顺利集成。可能存在一个独立的有界上下文。即使不存在有界上下文,也可能难以从其他包中顺畅地引用实体。
• Foreign elements don’t usually integrate smoothly. There may be a distinct BOUNDED CONTEXT. Even if not, it may be difficult to smoothly reference ENTITIES from your other packages.
• 它可能引入平台依赖性、编译器版本依赖性等等。
• It may introduce platform dependencies, compiler version dependencies, and so on.
现成的子域解决方案值得研究,但通常并不值得投入精力。我见过一些成功的案例,这些应用有着非常复杂的工作流程需求,它们使用了带有 API 接口的商用外部工作流程系统。我也见过一些成功的案例,它们将错误日志记录包深度集成到应用程序中。有时,通用的子域解决方案会以框架的形式打包,这些框架实现了一个非常抽象的模型,可以集成到你的应用程序中并进行定制。子组件越通用,其自身模型越精简,就越有可能发挥作用。
Off-the-shelf subdomain solutions are worth investigating, but they are usually not worth the trouble. I’ve seen success stories in applications with very elaborate workflow requirements that used commercially available external workflow systems with API hooks. I’ve also seen success with an error-logging package that was deeply integrated into the application. Sometimes GENERIC SUBDOMAIN solutions are packaged in the form of frameworks, which implement a very abstract model that can be integrated with and specialized for your application. The more generic the subcomponent, and the more distilled its own model, the better the chance that it will be useful.
优势
Advantages
• 比本土模型更成熟,并反映了许多人的见解
• More mature than a homegrown model and reflects many people’s insights
• 即时、高质量的文档
• Instant, high-quality documentation
缺点
Disadvantage
• 可能不太符合您的需求,或者可能对您的需求来说过于复杂。
• May not quite fit your needs or may be overengineered for your needs
汤姆·莱勒(20世纪50年代和60年代的喜剧歌曲作家)曾说过,数学成功的秘诀是:“抄袭!抄袭!不要让任何人的劳动成果逃过你的眼睛……只是要记得永远称之为研究。” 这在领域建模中,尤其是在攻击通用子领域时,是很好的建议。
Tom Lehrer (the comedic songwriter from the 1950s and 1960s) said the secret to success in mathematics was, “Plagiarize! Plagiarize. Let no one’s work evade your eyes. . . . Only be sure always to call it please, research.” Good advice in domain modeling, and especially when attacking a GENERIC SUBDOMAIN.
当模型被广泛应用时,这种方法效果最佳,例如《分析模式》(Fowler 1996)中的模型。(参见第11章。)
This works best when there is a widely distributed model, such as the ones in Analysis Patterns (Fowler 1996). (See Chapter 11.)
如果某个领域已经有了高度形式化且严谨的模型,那就使用它。会计和物理学就是两个很好的例子。这些领域不仅非常稳健和精简,而且被世界各地的人们广泛理解,从而减轻你现在和未来的培训负担。(参见第十章,关于使用既有形式体系。)
When the field already has a highly formalized and rigorous model, use it. Accounting and physics are two examples that come to mind. Not only are these very robust and streamlined, but they are widely understood by people everywhere, reducing your present and future training burden. (See Chapter 10, on using established formalisms.)
如果能找到一个自洽且满足自身需求的简化子集,不必非得照搬已发布模型的全部内容。但如果已经有成熟且文档齐全(最好是形式化)的模型可用,那就没必要重复造轮子了。
Don’t feel compelled to implement all aspects of a published model, if you can identify a simplified subset that is self-consistent and satisfies your needs. But in cases where there is a well-traveled and well-documented—or better yet, formalized—model available, it makes no sense to reinvent the wheel.
优势
Advantages
• 让核心团队能够专注于核心领域,因为那里需要和积累的知识最多。
• Keeps core team free to work on the CORE DOMAIN, where most knowledge is needed and accumulated.
• 允许在不永久扩大团队规模的情况下进行更多开发,但不会分散对核心领域的知识。
• Allows more development to be done without permanently enlarging the team, but without dissipating knowledge of the CORE DOMAIN.
•强制采用面向接口的设计,并有助于保持子域的通用性,因为规范是传递到外部的。
• Forces an interface-oriented design, and helps keep the subdomain generic, because the specification is being passed outside.
缺点
Disadvantages
• 仍然需要核心团队投入时间,因为界面、编码标准以及任何其他重要方面都需要进行沟通。
• Still requires time from the core team, because the interface, coding standards, and any other important aspects need to be communicated.
• 将所有权转移回内部会产生相当大的开销,因为需要理解代码。(不过,开销比专门的子域要小,因为通用模型通常不需要任何特殊背景知识即可理解。)
• Incurs significant overhead of transferring ownership back inside, because code has to be understood. (Still, overhead is less than for specialized subdomains, because a generic model presumably requires no special background to understand.)
• 代码质量可能存在差异。这可能是好事也可能是坏事,取决于两个团队的相对水平。
• Code quality can vary. This could be good or bad, depending on the relative caliber of the two teams.
自动化测试在外包中扮演着重要角色。应要求实施方为其交付的代码提供单元测试。一种真正有效的方法——有助于确保一定程度的质量、明确规范并简化重新集成——是为外包组件指定甚至编写自动化验收测试。此外,“外包实施”与“已发布的设计或模型”可以完美结合。
Automated tests can play an important role in outsourcing. The implementers should be required to provide unit tests for the code they deliver. A really powerful approach—one that helps ensure a degree of quality, clarifies the spec, and smooths reintegration—is to specify or even write automated acceptance tests for the outsourced components. Also, “outsourced implementation” can be an excellent combination with “published design or model.”
优势
Advantages
• 易于集成。
• Easy integration.
• 你只会得到你想要的,不会有任何额外的东西。
• You get just what you want and nothing extra.
• 可以指派临时承包商。
• Temporary contractors can be assigned.
缺点
Disadvantages
• 持续的维护和培训负担。
• Ongoing maintenance and training burden.
• 开发此类软件包所需的时间和成本很容易被低估。
• It is easy to underestimate the time and cost of developing such packages.
当然,这也与“已发布的设计或模型”完美结合。
Of course, this too combines well with “published design or model.”
通用亚域 这些模块适合尝试应用外部设计专业知识,因为它们不需要您深入了解您的核心专业领域,也不会提供学习该领域的绝佳机会。由于此类模块很少涉及专有信息或商业惯例,因此保密性问题也较少。通用子领域可以减轻那些不致力于深入了解该领域的人员的培训负担。
GENERIC SUBDOMAINS are the place to try to apply outside design expertise, because they do not require deep understanding of your specialized CORE DOMAIN, and they do not present a major opportunity to learn that domain. Confidentiality is of less concern, because little proprietary information or business practice will be involved in such modules. A GENERIC SUBDOMAIN lessens the training burden for those not committed to deep knowledge of the domain.
随着时间的推移,我相信我们对核心模型构成要素的理解会逐渐缩小,越来越多的通用模型将以已实现的框架形式出现,或者至少以已发布的模型或分析模式的形式存在。目前,我们仍然需要自行开发其中的大部分模型,但将它们从核心领域模型中分离出来具有巨大的价值。
Over time, I believe our ideas of what constitutes the CORE model will narrow, and more and more generic models will be available as implemented frameworks, or at least as published models or analysis patterns. For now, we still have to develop most of these ourselves, but there is great value in partitioning them from the CORE DOMAIN model.
我曾两次亲眼目睹项目中最优秀的开发人员花费数周时间来解决存储和转换带时区的时间的问题。虽然我总是对这种做法持怀疑态度,但有时却是必要的,而这两个项目恰恰提供了完美的对比。
Twice I’ve watched as the best developers on a project spent weeks of their time solving the problem of storing and converting times with time zones. While I’m always suspicious of such activities, sometimes it is necessary, and these two projects provide almost perfect contrast.
第一个项目是设计用于货物运输的调度软件。要安排国际运输,精确的时间计算至关重要,但由于所有此类调度都是以当地时间为准,因此如果不进行转换,就无法协调运输。
The first was an effort to design scheduling software for cargo shipping. To schedule international transports, it is critical to have accurate time calculations, and because all such schedules are tracked in local time, it is impossible to coordinate transports without conversions.
在明确了对这项功能的需求后,团队开始开发核心域,并利用现有的时间类别和一些虚拟数据完成了应用程序的早期迭代。随着应用程序的日趋成熟,他们发现现有的时间类别已无法满足需求,而且由于各国之间的差异以及国际日期变更线的复杂性,问题变得非常棘手。此时,他们的需求更加清晰,于是开始寻找现成的解决方案,但一无所获。他们别无选择,只能自行开发。
Having clearly established their need for this functionality, the team proceeded with development of the CORE DOMAIN and some early iterations of the application using the available time classes and some dummy data. As the application began to mature, it was clear that the existing time classes were not adequate, and that the problem was very intricate because of the variations between the many countries and the complexity of the International Date Line. With their requirements by now even clearer, they searched for an off-the-shelf solution, but found none. They had no option but to build it themselves.
这项任务需要研究和精密工程,因此团队领导指派了他们最优秀的程序员之一。但是这项任务不需要任何航运方面的专业知识,也不会培养这方面的知识,所以他们选择了一位以临时合同工身份参与该项目的程序员。
The task would require research and precision engineering, so the team leaders assigned one of their best programmers. But the task did not require any special knowledge of shipping and would not cultivate that knowledge, so they chose a programmer who was on the project on a temporary contract.
这位程序员并非从零开始。他研究了几个现有的时区实现方案,其中大多数都无法满足要求,于是决定采用 BSD Unix 的公共领域解决方案,该方案拥有一个复杂的数据库和一个用 C 语言实现的程序。他逆向工程了该方案的逻辑,并编写了一个数据库导入例程。
This programmer did not start from scratch. He researched several existing implementations of time zones, most of which did not meet requirements, and decided to adapt the public-domain solution from BSD Unix, which had an elaborate database and an implementation in C. He reverse-engineered the logic and wrote an import routine for the database.
问题比预期的还要棘手(例如,涉及特殊案例数据库的导入),但代码最终还是编写完成并与核心系统集成,产品也交付了。
The problem turned out to be even harder than expected (involving, for example, the import of databases of special cases), but the code got written and integrated with the CORE and the product was delivered.
另一个项目的情况则截然不同。一家保险公司正在开发一套新的理赔处理系统,计划记录各种事件发生的时间(例如车祸发生时间、冰雹天气发生时间等等)。这些数据将以本地时间记录,因此需要时区功能。
Things went very differently on the other project. An insurance company was developing a new claims-processing system, and planned to capture the times of various events (time of car crash, time of hail storm, and so on). This data would be recorded in local time, so time zone functionality was needed.
我到的时候,他们已经指派了一位资历尚浅但非常聪明的开发人员负责这项任务,尽管应用程序的具体需求尚未确定,甚至连最初的迭代版本都还没有尝试过。他尽职尽责地着手预先构建一个时区模型。
When I arrived, they had assigned a junior, but very smart, developer to the task, although the exact requirements of the app were still in play and not even an initial iteration had been attempted. He had dutifully set out to build a time zone model a priori.
由于不清楚具体需要什么,所以想当然地认为它应该足够灵活,能够应对任何情况。负责这项任务的程序员在面对如此棘手的问题时需要帮助,因此又指派了一位资深开发人员参与。他们编写了复杂的代码,但由于没有具体的应用程序使用它,所以代码是否正确运行始终是个未知数。
Not knowing what would be needed, it was assumed that it should be flexible enough to handle anything. The programmer assigned to the task needed help with such a difficult problem, so a senior developer was assigned to it also. They wrote complex code, but no specific application was using it, so it was never clear that the code worked correctly.
由于种种原因,该项目搁浅了,时区代码也从未被使用。但即便使用了,仅仅存储带有时区标签的本地时间可能就足够了,无需进行转换,因为这主要是参考数据,而非计算的基础。即使最终需要进行转换,所有数据都将来自北美,那里的时区转换相对简单。
The project ran aground for various reasons, and the time zone code was never used. But if it had been, simply storing local times tagged with the time zone might have been sufficient, even with no conversion, because this was primarily reference data and not the basis of computations. Even if conversion had turned out to be necessary, all the data was going to be gathered from North America, where time zone conversions are relatively simple.
对时区的过度关注主要代价是忽视了核心领域模型。如果将同样的精力投入到核心领域模型中,他们或许已经开发出了一个可运行的应用程序原型,并完成了初步测试。在工作领域模型的基础上。此外,参与该项目的开发人员长期致力于该项目,他们应该对保险领域非常熟悉,从而在团队内部积累了关键知识。
The main cost of this attention to the time zones was the neglect of the CORE DOMAIN model. If the same energy had been placed there, they might have produced a functioning prototype of their own application and a first cut at a working domain model. Furthermore, the developers involved, who were committed long-term to the project, should have been steeped in the insurance domain, building up critical knowledge within the team.
这两个项目做得好的一点是,它们将通用时区模型与核心域清晰地分离了。如果采用专门针对航运或保险的时区模型,就会将模型与这个通用支持模型耦合在一起,使核心部分更难理解(因为它会包含与时区无关的细节)。这也会使时区模块更难维护(因为维护人员必须了解核心部分及其与时区之间的相互关系)。
One thing both projects did right was to cleanly segregate the GENERIC time zone model from the CORE DOMAIN. A shipping-specific or insurance-specific model of time zones would have coupled the model to this generic supporting model, making the CORE harder to understand (because it would contain irrelevant detail about time zones). It would have made the time zone MODULE harder to maintain (because the maintainer would have to understand the CORE and its interrelationship with time zones).
我们这些技术人员往往喜欢处理像时区转换这样定义明确的问题,也很容易找到理由把时间花在这些事情上。但认真审视优先级通常会将我们引向核心领域。
We technical people tend to enjoy definable problems like time zone conversion, and we can easily justify spending our time on them. But a disciplined look at priorities usually points to the CORE DOMAIN.
请注意,虽然我强调了这些子域的通用性,但我并未提及代码的可重用性。现成的解决方案可能适用于特定情况,也可能不适用,但假设您是自行实现代码(无论是内部开发还是外包),则您无需特别关注这一点。该代码的可重用性。这将违背代码提炼的基本动机:你应该尽可能将精力投入到核心领域,并且仅在必要时才投入资源来支持通用子领域。
Note that while I have emphasized the generic quality of these subdomains, I have not mentioned the reusability of code. Off-the-shelf solutions may or may not make sense for a particular situation, but assuming that you are implementing the code yourself, in-house or outsourced, you should specifically not concern yourself with the reusability of that code. This would go against the basic motivation of distillation: that you should be applying as much of your effort to the CORE DOMAIN as possible and investing in supporting GENERIC SUB-DOMAINS only as necessary.
重用确实存在,但并非总是代码重用。模型重用通常是一种更高层次的重用方式,例如使用已发布的设计或模型。如果您需要创建自己的模型,它很可能在后续的相关项目中发挥作用。虽然此类模型的概念可能适用于许多情况,但您无需开发完全通用的模型。您可以仅对业务所需的部分进行建模和实现。
Reuse does happen, but not always code reuse. The model reuse is often a better level of reuse, as when you use a published design or model. And if you have to create your own model, it may well be valuable in a later related project. But while the concept of such a model may be applicable to many situations, you do not have to develop the model in its full generality. You can model and implement only the part you need for your business.
虽然设计时很少应该考虑可重用性,但必须严格遵循通用概念。引入行业特定的模型元素会带来两个代价。首先,它会阻碍未来的开发。虽然你现在只需要子域模型的一小部分,但你的需求会不断增长。如果在设计中引入任何不属于通用概念的内容,就会导致系统难以干净利落地扩展,除非你完全重建旧部分并重新设计使用它的其他模块。
Though you should seldom design for reusability, you must be strict about keeping within the generic concept. Introducing industry-specific model elements will have two costs. First, it will impede future development. Although you need only a small part of the subdomain model now, your needs will grow. By introducing anything to the design that is not part of the concept, you make it much more difficult to expand the system cleanly without completely rebuilding the older part and redesigning the other modules that use it.
第二个,也是更重要的原因是,这些行业特定的概念要么属于核心领域,要么属于它们自己的、更专业的子领域,而且这些专业模型比通用模型更有价值。
The second, and more important, reason is that those industry-specific concepts belong either in the CORE DOMAIN or in their own, more specialized, subdomains, and those specialized models are even more valuable than the generic ones.
敏捷流程通常要求通过尽早处理风险最高的任务来管理风险。极限编程(XP)尤其要求立即搭建并运行一个端到端的系统。这个初始系统通常会验证技术架构,因此很容易构建一个外围系统来处理一些辅助性的通用子域,因为这些系统通常更容易分析。但要小心;这样做可能会违背风险管理的初衷。
Agile processes typically call for managing risk by tackling the riskiest tasks early. XP specifically calls for getting an end-to-end system up and running immediately. This initial system often proves a technical architecture, and it is tempting to build a peripheral system that handles some supporting GENERIC SUBDOMAIN because these are usually easier to analyze. But be careful; this can defeat the purpose of risk management.
项目面临来自技术和领域建模两方面的风险,有些项目技术风险更大,有些项目领域建模风险更大。端到端系统只能在一定程度上降低风险,因为它只是实际系统中那些具有挑战性部分的雏形。领域建模风险很容易被低估。它可以表现为:无法预见的复杂性、缺乏业务专家资源,或者开发人员的关键技能存在差距。
Projects face risk from both sides, with some projects having greater technical risks and others greater domain modeling risks. The end-to-end system mitigates risk only to the extent that it is an embryonic version of the challenging parts of the actual system. It is easy to underestimate the domain modeling risk. It can take the form of unforeseen complexity, inadequate access to business experts, or gaps in key skills of the developers.
因此,除非团队拥有成熟的技能并且对领域非常熟悉,否则第一阶段的系统应该基于核心领域的某些部分,无论多么简单。
Therefore, except when the team has proven skills and the domain is very familiar, the first-cut system should be based on some part of the CORE DOMAIN, however simple.
同样的原则也适用于任何试图推进高风险任务的过程:核心领域之所以风险高,是因为它常常出乎意料地困难,而且没有它,项目就无法成功。
The same principle applies to any process that tries to push high-risk tasks forward: the CORE DOMAIN is high risk because it is often unexpectedly difficult and because without it, the project cannot succeed.
本章中的大多数提炼模式展示了如何修改模型和代码以提炼核心领域。然而,接下来的两个模式——领域愿景陈述和核心重点——则展示了如何通过少量投入,利用补充文档来改善沟通,提高对核心领域的认识,并集中开发精力……
Most of the distillation patterns in this chapter show how to change the model and code to distill the CORE DOMAIN. However, the next two patterns, DOMAIN VISION STATEMENT and HIGHLIGHTED CORE, show how the use of supplemental documents can, with a very minor investment, improve communication and awareness of the CORE and focus development effort. . . .
项目初期,模型通常尚未建立,但此时已迫切需要对其进行开发。在开发后期,需要对系统的价值进行解释,而这种解释并不需要对模型进行深入研究。此外,领域模型的关键方面可能跨越多个限界上下文,但根据定义,这些不同的模型无法通过构建来展现它们的共同关注点。
At the beginning of a project, the model usually doesn’t even exist, yet the need to focus its development is already there. In later stages of development, there is a need for an explanation of the value of the system that does not require an in-depth study of the model. Also, the critical aspects of the domain model may span multiple BOUNDED CONTEXTS, but by definition these distinct models can’t be structured to show their common focus.
许多项目团队都会为管理层撰写“愿景声明”。其中优秀的愿景声明会详细阐述应用程序将为组织带来的具体价值。有些愿景声明还会提及领域模型的创建是一项战略资产。通常情况下,项目获得资金后,愿景声明文件就会被束之高阁,从未在实际开发过程中使用,甚至技术人员也很少阅读。
Many project teams write “vision statements” for management. The best of these documents lay out the specific value the application will bring to the organization. Some mention the creation of the domain model as a strategic asset. Usually the vision statement document is abandoned after the project gets funding, and it is never used in the actual development process or even read by the technical staff.
领域愿景声明的格式与此类文档类似,但它侧重于领域模型的本质及其对企业的价值。管理人员和技术人员可以在开发的各个阶段直接使用它来指导资源分配、建模选择以及团队成员培训。如果领域模型服务于多个方面,该文档可以展示如何平衡各方利益。
A DOMAIN VISION STATEMENT is modeled after such documents, but it focuses on the nature of the domain model and how it is valuable to the enterprise. It can be used directly by the management and technical staff during all phases of development to guide resource allocation, to guide modeling choices, and to educate team members. If the domain model serves many masters, this document can show how their interests are balanced.
所以:
Therefore:
请简要描述核心领域及其价值(即“价值主张”)(约一页纸)。忽略那些无法将此领域模型与其他领域模型区分开来的方面。阐述该领域模型如何服务并平衡不同的利益。保持内容简洁明了。尽早撰写此陈述,并随着新见解的获得进行修改。
Write a short description (about one page) of the CORE DOMAIN and the value it will bring, the “value proposition.” Ignore those aspects that do not distinguish this domain model from others. Show how the domain model serves and balances diverse interests. Keep it narrow. Write this statement early and revise it as you gain new insight.
领域愿景声明可以作为指导方针,使开发团队在不断提炼模型和代码的过程中朝着共同的方向前进。它可以与非技术团队成员、管理层甚至客户共享(当然,如果其中包含专有信息则除外)。
A DOMAIN VISION STATEMENT can be used as a guidepost that keeps the development team headed in a common direction in the ongoing process of distilling the model and code itself. It can be shared with nontechnical team members, management, and even customers (except where it contains proprietary information, of course).
领域愿景声明 为团队指明共同方向。通常需要在高层级的陈述和代码或模型的完整细节之间架起一座桥梁……
A DOMAIN VISION STATEMENT gives the team a shared direction. Some bridge between the high-level STATEMENT and the full detail of the code or model will usually be needed. . . .
领域愿景声明从宏观层面界定了核心领域,但具体核心模型要素的确定则取决于个人的解读。除非团队内部沟通极其顺畅,否则单凭愿景声明本身几乎不会产生任何影响。
A DOMAIN VISION STATEMENT identifies the CORE DOMAIN in broad terms, but it leaves the identification of the specific CORE model elements up to the vagaries of individual interpretation. Unless there is an exceptionally high level of communication on the team, the VISION STATEMENT alone will have little impact.
即使团队成员大致了解核心领域的构成要素,不同的人挑选出的要素也不尽相同,甚至同一个人每天的理解也会有所差异。不断地筛选模型以识别关键部分会耗费大量精力,而这些精力本可以更好地用于设计思考,而且这还需要对模型有全面的了解。因此,必须让核心领域更容易被识别。
Even though team members may know broadly what constitutes the CORE DOMAIN, different people won’t pick out quite the same elements, and even the same person won’t be consistent from one day to the next. The mental labor of constantly filtering the model to identify the key parts absorbs concentration better spent on design thinking, and it requires comprehensive knowledge of the model. The CORE DOMAIN must be made easier to see.
对代码进行重大结构性更改是识别核心领域的理想方法,但短期内并非总是切实可行。事实上,如果没有团队目前所缺乏的视角,这种重大的代码更改很难实施。
Significant structural changes to the code are the ideal way of identifying the CORE DOMAIN, but they are not always practical in the short term. In fact, such major code changes are difficult to undertake without the very view the team is lacking.
模型组织结构的变化,例如划分通用子域以及本章稍后将要介绍的其他一些内容,可以让模块更好地讲述故事。但作为传达核心域的唯一途径,这过于雄心勃勃,不宜立即着手。
Structural changes in the organization of the model, such as partitioning GENERIC SUBDOMAINS and a few others to come later in this chapter, can allow the MODULES to tell the story. But as the only means of communicating the CORE DOMAIN, this is too ambitious to shoot for straight away.
您可能需要一种更轻量级的解决方案来补充这些激进的技术。您可能受到一些限制,无法将核心代码物理分离出来。或者,您可能正在处理现有代码,这些代码并没有很好地区分核心代码,但您确实需要看到核心代码,并分享这种视图,以便有效地重构并更好地提炼代码。即使在高级阶段,一些精心挑选的图表或文档也能为团队提供思维锚点和切入点。
You will probably need a lighter solution to supplement these aggressive techniques. You may have constraints that prevent you from physically separating the CORE. Or you may be starting out with existing code that does not differentiate the CORE well, but you really need to see the CORE, and share that view, to effectively refactor toward better distillation. And even at an advanced stage, a few carefully selected diagrams or documents provide mental anchor points and entry points for the team.
这些问题对于使用复杂 UML 模型的项目和那些(例如极限编程项目)很少维护外部文档并将代码作为模型主要存储库的项目同样存在。极限编程团队可能更倾向于极简主义,使这些补充内容更加随意和临时(例如,(例如,在墙上绘制的手绘图表,供所有人观看),但这些技术可以很好地融入到这个过程之中。
These issues arise equally for projects that use elaborate UML models and those (such as XP projects) that keep few external documents and use the code as the primary repository of the model. An Extreme Programming team might be more minimalist, keeping these supplements more casual and more transient (for example, a hand-drawn diagram on the wall for all to see), but these techniques can fold nicely into the process.
将模型中具有特殊权限的部分及其实现方式划出来,是对模型的一种反思,而非模型本身的一部分。任何能够让所有人都能轻松理解核心领域的技巧都可以。以下两种具体技巧可以代表这类解决方案。
Marking off a privileged part of a model, along with the implementation that embodies it, is a reflection on the model, not necessarily part of the model itself. Any technique that makes it easy for everyone to know the CORE DOMAIN will do. Two specific techniques can represent this class of solutions.
我通常会创建一份单独的文档来描述和解释核心领域。它可以很简单,例如列出最重要的概念对象;也可以是一组专注于这些对象的图表,展示它们之间最关键的关系;还可以从抽象层面或通过示例来阐述基本交互;可以使用 UML 类图或序列图、领域特有的非标准图表、精心撰写的文字说明,或者它们的组合。提炼文档并非完整的设计文档,而是一个极简的入口点,它概述并解释了核心领域,并指出了需要仔细审查特定部分的原因。读者可以从中获得各个部分如何相互关联的概览,并被引导到代码的相应部分以了解更多细节。
Often I create a separate document to describe and explain the CORE DOMAIN. It can be as simple as a list of the most essential conceptual objects. It can be a set of diagrams focused on those objects, showing their most critical relationships. It can walk through the fundamental interactions at an abstract level or by example. It can use UML class or sequence diagrams, nonstandard diagrams particular to the domain, carefully worded textual explanations, or combinations of these. A distillation document is not a complete design document. It is a minimalist entry point that delineates and explains the CORE and suggests reasons for closer scrutiny of particular pieces. The reader is given a broad view of how the pieces fit and guided to the appropriate part of the code for more details.
因此(作为重点核心的一种形式):
Therefore (as one form of HIGHLIGHTED CORE):
写一份非常简短的文件(三到七页,篇幅不长),描述核心领域以及核心元素之间的主要相互作用。
Write a very brief document (three to seven sparse pages) that describes the CORE DOMAIN and the primary interactions among CORE elements.
单独文件的所有常见风险依然存在。
All the usual risks of separate documents apply.
1.该文件可能无法保存。
1. The document may not be maintained.
2.该文件可能无法阅读。
2. The document may not be read.
3.通过增加信息来源,文档可能会违背其自身简化复杂性的目的。
3. By multiplying the information sources, the document may defeat its own purpose of cutting through complexity.
降低这些风险的最佳方法是采用极简主义。避免繁琐的细节,专注于核心抽象概念及其相互作用,可以让文档老化得更慢,因为模型的这一层面通常更稳定。
The best way to limit these risks is to be absolutely minimalist. Staying away from mundane detail and focusing on the central abstractions and their interactions allows the document to age more slowly, because this level of the model is usually more stable.
编写这份文档时,要确保团队中的非技术成员也能理解。将其作为共享视图,清晰地阐明每个人的职责。我们需要了解,并且需要制定一个指南,以便所有团队成员可以开始探索模型和代码。
Write the document to be understood by the nontechnical members of the team. Use it as a shared view that delineates what every-one needs to know, and a guide by which all team members may start their exploration of the model and code.
在一家大型保险公司负责一个项目的第一天,我就拿到了一份“领域模型”文档,这份长达两百页的文件是他们从一个行业联盟斥巨资购买的。我花了几天时间才看完一堆杂乱无章的类图,内容涵盖了从保险单的详细组成到极其抽象的人际关系模型等方方面面。这些模型的建模质量参差不齐,从高中生水平到相当不错都有(有些甚至描述了业务规则,至少在附带的文字说明中是这样)。但是,该从何入手呢?整整两百页啊!
On my first day on a project at a major insurance company, I was given a copy of the “domain model,” a two-hundred-page document, purchased at great expense from an industry consortium. I spent a few days wading through a jumble of class diagrams covering everything from the detailed composition of insurance policies to extremely abstract models of relationships between people. The quality of the factoring of these models ranged from high-school project to rather good (a few even described business rules, at least in the accompanying text). But where to start? Two hundred pages.
项目文化非常推崇抽象框架构建,我的前任们专注于构建一个非常抽象的模型,用来描述人与人、人与物、人与活动或协议之间的关系。这确实是一个不错的分析,他们运用该模型进行的实验也达到了学术研究项目的水准。但这却让我们离保险申请还差得远。
The project culture heavily favored abstract framework building, and my predecessors had focused on a very abstract model of the relationship of people with each other, with things, and with activities or agreements. It was actually a nice analysis of these relationships, and their experiments with the model had the quality of an academic research project. But it wasn’t getting us anywhere near an insurance application.
我的第一反应是开始大幅削减,找到一个核心领域作为备选方案,然后重构它,并在此基础上逐步引入其他复杂功能。但管理层对这种做法感到担忧。这份文件权威性极高。它的编写汇集了业内各领域的专家,而且无论如何,他们支付给这个联盟的费用远高于支付给我的薪水,所以他们不太可能认真考虑我提出的激进变革建议。但我知道,我们必须先就核心领域达成共识,让所有人的努力都集中在这个核心领域上。
My first instinct was to start slashing, finding a small CORE DOMAIN to fall back on, then refactoring that and reintroducing other complexities as we went. But the management was alarmed by this attitude. The document was invested with great authority. Its production had involved experts from across the industry, and in any event they had paid the consortium far more than they were paying me, so they were unlikely to weigh my recommendations for radical change too heavily. But I knew we had to get a shared picture of our CORE DOMAIN and get everyone’s efforts focused on that.
我没有进行重构,而是通读了文档,并在一位对保险行业以及我们要开发的应用程序的具体需求都非常了解的业务分析师的帮助下,确定了其中几个关键章节,这些章节阐述了我们需要处理的核心差异化概念。我绘制了模型的导航图,清晰地展示了核心功能及其与辅助功能之间的关系。
Instead of refactoring, I went through the document and, with the help of a business analyst who knew a great deal about the insurance industry in general and the requirements of the application we were to build in particular, I identified the handful of sections that presented the essential, differentiating concepts we needed to work with. I provided a navigation of the model that clearly showed the CORE and its relationship to supporting features.
从这个角度出发,一项新的原型设计工作开始了,并很快产生了一个简化的应用程序,该应用程序展示了一些所需的功能。
A new prototyping effort started from this perspective, and quickly yielded a simplified application that demonstrated some of the required functionality.
只需几个标签和一些黄色荧光笔,两磅可回收纸张就变成了一项商业资产。
Two pounds of recyclable paper was turned into a business asset by a few page tabs and some yellow highlighter.
这种技巧并非纸上对象图所独有。大量使用 UML 图的团队可以使用“构造型”来识别核心元素。而仅以代码作为模型存储库的团队则可能使用注释(例如 Java 文档),或者在其开发环境中使用某些工具。具体采用哪种技巧并不重要,只要开发人员能够轻松区分核心域内外的内容即可。
This technique is not specific to object diagrams on paper. A team that uses UML diagrams extensively could use a “stereotype” to identify core elements. A team that uses the code as the sole repository of the model might use comments, maybe structured as Java Doc, or might use some tool in its development environment. The particular technique doesn’t matter, as long as a developer can effortlessly see what is in and what is out of the CORE DOMAIN.
因此(作为另一种形式的重点突出):
Therefore (as another form of HIGHLIGHTED CORE):
在模型的主存储库中标记核心域的每个元素,无需特别阐明其作用。让开发人员能够轻松了解哪些内容包含在核心域中,哪些内容不包含在核心域中。
Flag each element of the CORE DOMAIN within the primary repository of the model, without particularly trying to elucidate its role. Make it effortless for a developer to know what is in or out of the CORE.
现在,对于使用该模型的人来说,核心域已经清晰可见,而且维护工作量相当小,至少在模型分解得足够精细,能够区分各个部分的贡献的情况下是如此。
The CORE DOMAIN is now clearly visible to those working with the model, with a fairly small effort and low maintenance, at least to the extent that the model is factored fine enough to distinguish the contributions of parts.
理论上,在极限编程(XP)项目中,任何一对程序员(两个共同工作的程序员)都可以修改系统中的任何代码。但在实践中,有些修改会产生重大影响,需要更多的协商和协调。在基础设施层工作时,修改的影响可能很明显,但在通常组织架构下的领域层,影响可能并不那么显而易见。
Theoretically on an XP project, any pair (two programmers working together) can change any code in the system. In practice, some changes have major implications, and call for more consultation and coordination. When working in the infrastructure layer, the impact of a change may be clear, but it may not be so obvious in the domain layer, as typically organized.
借助核心域的概念,这种影响可以清晰地展现出来。对核心域模型的更改应该会产生重大影响。对广泛使用的通用元素的更改可能需要大量的代码更新,但它们仍然不会像核心域的更改那样带来概念上的转变。
With the concept of the CORE DOMAIN, this impact can be made clear. Changes to the model of the CORE DOMAIN should have a big effect. Changes to widely used generic elements may require a lot of code updating, but they still shouldn’t create the conceptual shift that CORE changes do.
将提炼文档作为指导。当开发人员意识到提炼文档本身需要修改才能与他们的代码或模型变更保持同步时,就需要进行咨询。要么他们正在从根本上改变核心领域元素,要么关系,或者它们正在改变核心的边界,包括纳入或排除某些不同的内容。必须通过团队使用的任何沟通渠道,包括分发新版本的提炼文档,将模型变更传达给整个团队。
Use the distillation document as a guide. When developers realize that the distillation document itself requires change to stay in sync with their code or model change, then consultation is called for. Either they are fundamentally changing the CORE DOMAIN elements or relationships, or they are changing the boundaries of the CORE, including or excluding something different. Dissemination of the model change to the whole team is necessary by whatever communication channels the team uses, including distribution of a new version of the distillation document.
如果蒸馏文档概述了核心领域的要点,那么它就可以作为模型变更重要性的实际指标。当模型或代码变更影响到蒸馏文档时,需要与其他团队成员协商。变更完成后,需要立即通知所有团队成员,并发布新版本的文档。核心领域之外的变更或蒸馏文档中未包含的细节变更,无需协商或通知即可集成,其他成员将在工作中遇到这些变更。此时,开发人员拥有极限编程(XP)所倡导的完全自主权。
If the distillation document outlines the essentials of the CORE DOMAIN, then it serves as a practical indicator of the significance of a model change. When a model or code change affects the distillation document, it requires consultation with other team members. When the change is made, it requires immediate notification of all team members, and the dissemination of a new version of the document. Changes outside the CORE or to details not included in the distillation document can be integrated without consultation or notification and will be encountered by other members in the course of their work. Then the developers have the full autonomy that XP suggests.
尽管愿景声明和重点核心内容提供了信息和指导,但它们实际上并未修改模型或代码本身。划分通用子域可以从物理层面移除一些干扰元素。接下来的模式将探讨如何从结构上改变模型和设计本身,使核心域更加清晰易懂、便于管理……
Although the VISION STATEMENT and HIGHLIGHTED CORE inform and guide, they do not actually modify the model or the code itself. Partitioning GENERIC SUBDOMAINS physically removes some distracting elements. The next patterns look at ways to structurally change the model and the design itself to make the CORE DOMAIN more visible and manageable. . . .
封装机制是面向对象设计的标准原则之一。将复杂的算法隐藏在名称清晰明了的方法中,可以将“做什么”与“怎么做”分离。这种技术使设计更易于理解和使用。然而,它也存在一些固有的局限性。
Encapsulating mechanisms is a standard principle of object-oriented design. Hiding complex algorithms in methods with intention-revealing names separates the “what” from the “how.” This technique makes a design simpler to understand and use. Yet it runs into natural limits.
计算有时会变得过于复杂,导致设计臃肿。概念上的“是什么”被机制上的“如何做”所掩盖。大量提供问题解决方法的算法反而模糊了表达问题本身的方法。
Computations sometimes reach a level of complexity that begins to bloat the design. The conceptual “what” is swamped by the mechanistic “how.” A large number of methods that provide algorithms for resolving the problem obscure the methods that express the problem.
这种程序数量的激增是模型存在问题的征兆。通过重构以获得更深入的洞察,可以构建出一个更适合解决问题的模型和设计。首先要寻找的解决方案是简化计算机制的模型。但有时,我们会发现机制的某些部分本身在概念上是连贯的。这种概念上的计算可能不会包含所有你需要的复杂计算。我们说的不是某种包罗万象的“计算器”。但提取出连贯的部分应该能让剩余的机制更容易理解。
This proliferation of procedures is a symptom of a problem in the model. Refactoring toward deeper insight can yield a model and design whose elements are better suited to solving the problem. The first solution to seek is a model that makes the computation mechanism simple. But now and then the insight emerges that some part of the mechanism is itself conceptually coherent. This conceptual computation will probably not include all of the messy computations you need. We are not talking about some kind of catch-all “calculator.” But extracting the coherent part should make the remaining mechanism easier to understand.
所以:
Therefore:
将概念上连贯的机制划分到一个独立的轻量级框架中。尤其要注意形式化描述或文档完善的算法分类。通过一个能够揭示意图的接口来展示框架的功能。现在,该领域的其他成员可以专注于表达问题(“是什么”),而将解决方案的复杂细节(“如何做”)委托给框架。
Partition a conceptually COHESIVE MECHANISM into a separate lightweight framework. Particularly watch for formalisms or well-documented categories of algorithms. Expose the capabilities of the framework with an INTENTION-REVEALING INTERFACE. Now the other elements of the domain can focus on expressing the problem (“what”), delegating the intricacies of the solution (“how”) to the framework.
这些分离的机制随后被放置在各自的辅助角色中,从而留下一个更小、更具表现力的核心域,该核心域通过界面以更声明式的方式使用该机制。
These separated mechanisms are then placed in their supporting roles, leaving a smaller, more expressive CORE DOMAIN that uses the mechanism through the interface in a more declarative style.
认识到标准算法或形式化方法可以将设计中的一些复杂性转化为一套经过研究的概念。有了这样的指导,我们就能充满信心地实现解决方案,减少试错。我们可以指望其他开发人员了解它,或者至少能够找到相关信息。这类似于已发布的通用子域带来的好处。 模型虽然存在,但由于计算机科学的这一层面研究更为深入,因此文档化的算法或形式化计算可能更为常见。然而,大多数情况下,您仍然需要创建新的东西。务必使其专注于计算本身,避免混入表达性领域模型。职责分工明确:核心领域或通用子领域的模型阐述事实、规则或问题;而内聚机制则根据模型的指示解决规则或完成计算。
Recognizing a standard algorithm or formalism moves some of the complexity of the design into a studied set of concepts. With such a guide, we can implement a solution with confidence and little trial and error. We can count on other developers knowing about it or at least being able to look it up. This is similar to the benefits of a published GENERIC SUBDOMAIN model, but a documented algorithm or formal computation may be found more often because this level of computer science has been studied more. Still, more often than not you will have to create something new. Make it narrowly focused on the computation and avoid mixing in the expressive domain model. There is a separation of responsibilities: The model of the CORE DOMAIN or a GENERIC SUBDOMAIN formulates a fact, rule, or problem. A COHESIVE MECHANISM resolves the rule or completes the computation as specified by the model.
我曾在一个项目中经历过这个过程,该项目需要一个相当复杂的组织结构图模型。这个模型展现了员工之间的上下级关系,以及他们所属的组织分支,并提供了一个可以提出和解答相关问题的界面。由于大多数问题都类似于“在这个层级中,谁有权批准这件事?”或者“在这个部门里,谁能够处理这类问题?”,团队意识到,大部分复杂性都来自于遍历组织树的特定分支,寻找特定的人员或关系。而这正是图论这种成熟的形式化方法所擅长解决的问题。图论由一系列节点组成,节点之间通过弧线(称为边)连接,并包含遍历图所需的规则和算法。
I went through this process on a project that needed a fairly elaborate model of an organization chart. This model represented the fact that one person worked for another, and in which branches of the organization, and it provided an interface by which relevant questions might be asked and answered. Because most of these questions were along the lines of “Who, in this chain of command, has authority to approve this?” or “Who, in this department, is capable of handling an issue like this?” the team realized that most of the complexity involved traversing specific branches of the organizational tree, searching for specific people or relationships. This is exactly the kind of problem solved by the well-developed formalism of a graph, a set of nodes connected by arcs (called edges) and the rules and algorithms needed to traverse the graph.
分包商实现了一个图遍历框架,作为一种内聚机制。该框架使用了大多数计算机科学家熟悉的标准图术语和算法,这些术语和算法在教科书中都有详尽的记载。他并没有实现一个完全通用的图。它只是该概念框架的一个子集,涵盖了我们组织模型所需的功能。而且,由于采用了意图揭示界面,获取答案的方式并非主要关注点。
A subcontractor implemented a graph traversal framework as a COHESIVE MECHANISM. This framework used standard graph terminology and algorithms familiar to most computer scientists and abundantly documented in textbooks. By no means did he implement a fully general graph. It was a subset of that conceptual framework that covered the features needed for our organization model. And with an INTENTION-REVEALING INTERFACE, the means by which the answers are obtained are not a primary concern.
现在,组织模型可以简单地用标准图论术语表述为:每个人都是一个节点,人与人之间的每一种关系都是连接这些节点的一条边(弧)。也就是说,图论框架内的机制可以找到任意两个人之间的关系。
Now the organization model could simply state, using standard graph terminology, that each person is a node, and that each relationship between people is an edge (arc) connecting those nodes. After that, presumably, mechanisms within the graph framework could find the relationship between any two people.
如果将此机制整合到领域模型中,将会给我们带来两方面的损失。首先,模型会与特定的问题解决方法绑定,限制未来的选择。更重要的是,组织模型会变得极其复杂且混乱。将机制和模型分离,使得描述组织结构的方式更加清晰明了,也更符合声明式编程的理念。此外,用于图操作的复杂代码被隔离在一个纯粹的机制框架中,该框架基于成熟的算法,可以独立进行维护和单元测试。
If this mechanism had been incorporated into the domain model, it would have cost us in two ways. The model would have been coupled to a particular method of solving the problem, limiting future options. More important, the model of an organization would have been greatly complicated and muddied. Keeping mechanism and model separate allowed a declarative style of describing organizations that was much clearer. And the intricate code for graph manipulation was isolated in a purely mechanistic framework, based on proven algorithms, that could be maintained and unit-tested in isolation.
内聚机制的另一个例子是构建规范对象并支持其基本比较和组合操作的框架。通过采用这样的框架,核心域和通用子域可以用该模式中描述的清晰易懂的语言来声明它们的规范(参见第 10 章)。执行比较和组合所涉及的复杂操作可以交给框架来处理。
Another example of a COHESIVE MECHANISM would be a framework for constructing SPECIFICATION objects and supporting the basic comparison and combination operations expected of them. By employing such a framework, the CORE DOMAIN and GENERIC SUBDOMAINS can declare their SPECIFICATIONS in the clear, easily understood language described in that pattern (see Chapter 10). The intricate operations involved in carrying out the comparisons and combinations can be left to the framework.
通用子域和内聚机制的共同目标都是为了减轻核心域的负担。区别在于它们各自承担的责任性质。通用子域基于一个表达模型,该模型代表了团队对领域理解的某些方面。在这方面,它与核心域并无二致,只是不那么核心、不那么重要、不那么专业化。内聚机制并不代表领域本身;它解决的是表达模型提出的一些棘手的计算问题。
Both GENERIC SUBDOMAINS and COHESIVE MECHANISMS are motivated by the same desire to unburden the CORE DOMAIN. The difference is the nature of the responsibility taken on. A GENERIC SUBDOMAIN is based on an expressive model that represents some aspect of how the team views the domain. In this it is no different than the CORE DOMAIN, just less central, less important, less specialized. A COHESIVE MECHANISM does not represent the domain; it solves some sticky computational problem posed by the expressive models.
模型提出方案;凝聚机制进行处置。
A model proposes; a COHESIVE MECHANISM disposes.
实际上,除非你识别出已正式发表的计算,否则这种区别通常并不纯粹,至少一开始不是。在后续的重构过程中,它可能会被提炼成一个更纯粹的机制。或者被转化为一个通用子域,其中包含一些以前未被认识的模型概念,这将使机制变得简单。
In practice, unless you recognize a formalized, published computation, this distinction is usually not pure, at least not at first. In successive refactoring it could either be distilled into a purer mechanism or be transformed into a GENERIC SUBDOMAIN with some previously unrecognized model concepts that would make the mechanism simple.
几乎总是应该将机制从核心领域中移除。唯一的例外是,当机制本身是专有的,并且是软件价值的关键组成部分时。高度专业化的算法有时会出现这种情况。例如,如果一个航运物流应用程序的显著特点之一是其高效的调度算法,那么该机制就可以被视为概念核心的一部分。我曾经在一家投资银行参与过一个项目,其中用于风险评估的高度专有算法无疑属于核心领域。(事实上,这些算法的保密程度非常高,甚至大多数核心开发人员都无权查看。)当然,这些算法很可能是一组真正用于预测风险的规则的特定实现。更深入的分析可能会得出更深层次的模型,该模型能够明确地表达这些规则,并封装一个求解机制。
You almost always want to remove MECHANISMS from the CORE DOMAIN. The one exception is when a MECHANISM is itself proprietary and a key part of the value of the software. This is sometimes the case with highly specialized algorithms. For example, if one of the distinguishing features of a shipping logistics application were a particularly effective algorithm for working out schedules, that MECHANISM could be considered part of the conceptual CORE. I once worked on a project at an investment bank in which highly proprietary algorithms for rating risk were definitely in the CORE DOMAIN. (In fact, they were held so closely that even most of the CORE developers were not allowed to see them.) Of course, these algorithms are probably a particular implementation of a set of rules that really predict risk. Deeper analysis might lead to a deeper model that would allow those rules to be explicit, with an encapsulated solving mechanism.
但这只是设计上的又一次渐进式改进,留待日后讨论。是否进行下一步的决定将基于成本效益分析:制定新设计有多难?现有设计理解和修改起来有多难?对于预期执行这项工作的人员来说,更先进的设计会有多大的便利性?当然,还有人知道新模型会是什么样子吗?
But that would be another incremental improvement in the design, for another day. The decision as to whether to go that next step would be based on a cost-benefit analysis: How difficult would it be to work out that new design? How difficult is the current design to understand and modify? How much easier would it be with a more advanced design, for the type of people who would be expected to do the work? And of course, does anyone have any idea what form the new model might take?
事实上,在我们完成上一个例子中的组织模型一年后,其他开发人员对其进行了重新设计,取消了图框架的分离。他们认为,增加对象数量以及将机制分离到单独包中带来的复杂性都是没有必要的。相反,他们将节点行为添加到了组织实体的父类中。尽管如此,他们他们保留了组织模型的声明式公共接口。他们甚至将机制封装在了组织实体内部。
Actually, a year after we completed the organization model in the previous example, other developers redesigned it to eliminate the separation of the graph framework. They felt the increased object count and the complication of separating the MECHANISM into a separate package were not warranted. Instead, they added node behavior to the parent class of the organizational ENTITIES. Still, they retained the declarative public interface of the organization model. They even kept the MECHANISM encapsulated, within the organizational ENTITIES.
这些完整的循环很常见,但它们并不会回到起点。最终结果通常会形成一个更深层次的模型,该模型能更清晰地区分事实、目标和机制。务实的重构保留了中间阶段的重要优点,同时剔除了不必要的复杂性。
These full circles are common, but they do not return to their starting point. The end result is usually a deeper model that more clearly differentiates facts, goals, and MECHANISMS. Pragmatic refactoring retains the important virtues of the intermediate stages while shedding the unneeded complications.
声明式设计和“声明式风格”是第十章的主题,但这种设计风格值得在本章关于战略提炼的部分中特别提及。提炼的价值在于能够看清自己正在做什么:直击本质,而不被无关的细节所干扰。当支撑设计提供了一种简洁的语言来表达核心的概念和规则,同时封装了计算或执行这些概念和规则的方法时,核心领域的重要组成部分就可以采用声明式风格。
Declarative design and “declarative style” is a topic of Chapter 10, but that design style deserves special mention in this chapter on strategic distillation. The value of distillation is being able to see what you are doing: cutting to the essence without being distracted by irrelevant detail. Important parts of the CORE DOMAIN may be able to follow a declarative style, when the supporting design provides an economical language for expressing the concepts and rules of the CORE while encapsulating the means of computing or enforcing them.
当内聚机制通过意图揭示型接口提供访问,并具备概念一致的断言和无副作用的函数时,它们的作用最为显著。机制和灵活的设计使得核心领域能够做出有意义的陈述,而不是调用晦涩难懂的函数。但当核心领域本身的一部分突破到深层模型,并开始像一种语言一样,能够灵活简洁地表达最重要的应用场景时,才能获得真正的回报。
COHESIVE MECHANISMS are by far most useful when they provide access through an INTENTION-REVEALING INTERFACE, with conceptually coherent ASSERTIONS and SIDE-EFFECT-FREE FUNCTIONS. MECHANISMS and supple designs allow the CORE DOMAIN to make meaningful statements rather than calling obscure functions. But an exceptional payoff comes when part of the CORE DOMAIN itself breaks through to a deep model and starts to function as a language that can express the most important application scenarios flexibly and concisely.
深度模型通常伴随着相应的柔性设计。当柔性设计成熟时,它会提供一套易于理解的元素,这些元素可以像单词组成句子一样,以明确无误的方式组合起来,完成复杂的任务或表达复杂的信息。此时,客户端代码会采用声明式风格,并且可以更加精简。
A deep model often comes with a corresponding supple design. When a supple design reaches maturity, it provides an easily understood set of elements that can be combined unambiguously to accomplish complex tasks or express complex information, just as words are combined into sentences. At that point, client code takes on a declarative style and can be much more distilled.
去除通用子域可以减少冗余,并增强内聚机制。 旨在封装复杂操作。这使得模型更加聚焦,减少了对用户活动方式无益的干扰因素。但你不太可能在领域模型中找到所有非核心内容的理想归宿。分离核心(SEGREGATED CORE)采用直接的方法,从结构上划分出核心领域……
Factoring out GENERIC SUBDOMAINS reduces clutter, and COHESIVE MECHANISMS serve to encapsulate complex operations. This leaves behind a more focused model, with fewer distractions that add no particular value to the way users conduct their activities. But you are unlikely ever to find good homes for everything in the domain model that is not CORE. The SEGREGATED CORE takes a direct approach to structurally marking off the CORE DOMAIN....
模型中的元素可能部分服务于核心领域,部分扮演辅助角色。核心元素可能与通用元素紧密耦合。核心的概念凝聚力可能不强或不明显。所有这些混乱和纠缠都会阻碍核心的发挥。设计者无法清晰地看到最重要的关系,从而导致设计薄弱。
Elements in the model may partially serve the CORE DOMAIN and partially play supporting roles. CORE elements may be tightly coupled to generic ones. The conceptual cohesion of the CORE may not be strong or visible. All this clutter and entanglement chokes the CORE. Designers can’t clearly see the most important relationships, leading to a weak design.
通过去除通用亚结构域,可以清除结构域中一些模糊的细节,使核心结构更加清晰可见。但是,识别和阐明所有这些亚结构域是一项艰巨的工作,而且其中一些似乎并不值得费力。与此同时,至关重要的核心结构域仍然与残基纠缠在一起。
By factoring out GENERIC SUBDOMAINS, you clear away some of the obscuring detail from the domain, making the CORE more visible. But it is hard work identifying and clarifying all these subdomains, and some of them don’t seem worth the trouble. Meanwhile, the all-important CORE DOMAIN is left entangled with the residue.
所以:
Therefore:
重构模型,将核心概念与辅助元素(包括定义不明确的元素)分离,增强核心的内聚性,同时降低其与其他代码的耦合度。将所有通用或辅助元素提取到其他对象中,并将它们放入其他包中,即使这意味着需要以分离高度耦合元素的方式重构模型。
Refactor the model to separate the CORE concepts from supporting players (including ill-defined ones) and strengthen the cohesion of the CORE while reducing its coupling to other code. Factor all generic or supporting elements into other objects and place them into other packages, even if this means refactoring the model in ways that separate highly coupled elements.
这基本上是将我们应用于通用子域的原理反过来应用。对于我们的应用而言至关重要的内聚子域可以被识别出来,并划分成各自独立的、连贯的模块。如何处理剩余的未分化部分固然重要,但并非最关键的。它可以基本保持原状,也可以根据显著的类别进行分类。最终,越来越多的剩余部分可以被分解为通用子域,但在短期内,任何简便的解决方案都可以,只要能够保持对分离核心的关注即可。
This is basically taking the same principles we applied to GENERIC SUBDOMAINS but from the other direction. The cohesive subdomains that are central to our application can be identified and partitioned into coherent packages of their own. What is done with the undifferentiated mass left behind is important, but not as important. It can be left more or less where it was, or placed into packages based on prominent classes. Eventually, more and more of the residue can be factored into GENERIC SUBDOMAINS, but in the short term any easy solution will do, just so the focus on the SEGREGATED CORE is retained.
将架构重构为隔离核心架构通常需要以下步骤:
The steps needed to refactor to SEGREGATED CORE are typically something like these:
1.确定一个核心子域(可能参考提炼文档)。
1. Identify a CORE subdomain (possibly drawing from the distillation document).
2. 将相关的类移到一个新的模块中,该模块以关联它们的概念命名。
2. Move related classes to a new MODULE, named for the concept that relates them.
3.重构代码,剔除与概念不直接相关的数据和功能。将移除的部分放入其他包中的(可能是新的)类中。尽量将它们放在概念相关的任务中,但不要过于追求完美。重点在于清理核心子域,并确保它对其他包的引用清晰明确、一目了然。
3. Refactor code to sever data and functionality that are not directly expressions of the concept. Put the removed aspects into (possibly new) classes in other packages. Try to place them with conceptually related tasks, but don’t waste too much time being perfect. Keep focused on scrubbing the CORE subdomain and making the references from it to other packages explicit and self-explanatory.
4.重构新分离的核心模块,使其关系和交互更简洁、更具沟通性,并尽量减少和明确其与其他模块的关系。(这将成为一项持续的重构目标。)
4. Refactor the newly SEGREGATED CORE MODULE to make its relationships and interactions simpler and more communicative, and to minimize and clarify its relationships with other MODULES. (This becomes an ongoing refactoring objective.)
5.对另一个CORE子域重复上述步骤,直到SEGREGATED CORE完成。
5. Repeat with another CORE subdomain until the SEGREGATED CORE is complete.
分离核心有时会使与紧密耦合的非核心类的关系更加模糊甚至更加复杂,但这种代价与澄清核心领域并使其更容易工作所带来的好处相比是微不足道的。
Segregating the CORE will sometimes make relationships with tightly coupled non-CORE classes more obscure or even more complicated, but that cost is outweighed by the benefit of clarifying the CORE DOMAIN and making it much easier to work on.
分离核心有助于增强核心域的内聚性。分解模型的方法有很多,有时在创建分离核心的过程中,可能会破坏一个原本内聚性良好的模块,牺牲其内聚性以增强核心域的内聚性。但这最终会带来净收益,因为企业软件的最大附加值来自于模型中企业特有的方面。
The SEGREGATED CORE will let you enhance the cohesion of that CORE DOMAIN. There are many meaningful ways of breaking down a model, and sometimes in the creation of a SEGREGATED CORE a nicely cohesive MODULE may be broken, sacrificing that cohesion for the sake of bringing out the cohesiveness of the CORE DOMAIN. This is a net gain, because the greatest value-added of enterprise software comes from the enterprise-specific aspects of the model.
当然,另一个代价是,隔离核心需要大量工作。必须承认,采用隔离核心的决策可能会让开发人员疲于应对整个系统的变更。
The other cost, of course, is that segregating the CORE is a lot of work. It must be acknowledged that a decision to go to a SEGREGATED CORE will potentially absorb developers in changes all over the system.
当系统存在一个至关重要的大型有界上下文,但模型的核心部分却被大量的支持功能所掩盖时,就应该剔除分离核心。
The time to chop out a SEGREGATED CORE is when you have a large BOUNDED CONTEXT that is critical to the system, but where the essential part of the model is being obscured by a great deal of supporting capability.
与许多战略设计决策一样,整个团队必须共同转向分离式核心架构。这一步骤需要一个团队决策流程,以及一个纪律严明、协调一致的团队来执行决策。难点在于如何确保每个人都使用相同的核心定义,同时又不使该决策僵化。由于核心领域会像设计的其他方面一样不断演进,因此,使用分离式核心架构的经验将有助于我们更深入地了解哪些是核心要素,哪些是辅助要素。这些见解应该反过来帮助我们完善核心领域和分离式核心模块的定义。
As with many strategic design decisions, an entire team must move to a SEGREGATED CORE together. This step requires a team decision process and a team disciplined and coordinated enough to carry out the decision. The challenge is to constrain everyone to use the same definition of the CORE while not freezing that decision. Because the CORE DOMAIN evolves just like every other aspect of a design, experience working with a SEGREGATED CORE will lead to new insights into what is essential and what is a supporting element. Those insights should feed back into a refined definition of the CORE DOMAIN and of the SEGREGATED CORE MODULES.
这意味着必须持续地与团队分享新的见解,但个人(或编程搭档)不能单方面根据这些见解采取行动。无论采用何种联合决策流程,无论是共识决策还是团队领导指令,都必须足够灵活,以便反复进行调整。沟通必须足够有效,才能确保所有人对核心目标保持一致。
This means that new insights must be shared with the team on an ongoing basis, but an individual (or programming pair) cannot act on those insights unilaterally. Whatever the process is for joint decisions, whether consensus or team leader directive, it must be agile enough to make repeated course corrections. Communication must be effective enough to keep everyone together in one view of the CORE.
我们以图 15.2所示的模型作为货物运输协调软件的基础。
We start with the model shown in Figure 15.2 as the basis of software for cargo shipping coordination.
图 15.2
Figure 15.2
请注意,与实际应用可能需要的情况相比,这只是一个高度简化的模型。一个更贴近实际的模型对于示例来说过于复杂。因此,尽管这个示例可能不足以让我们想到隔离核心,但请发挥想象力,将此模型视为过于复杂,难以整体理解和处理。
Note that this is highly simplified compared to what would likely be needed for a real application. A realistic model would be too cumbersome for an example. Therefore, although this example may not be complicated enough to drive us to a SEGREGATED CORE, take a leap of imagination to treat this model as being too complex to interpret easily and deal with as a whole.
那么,这种运输模式的本质是什么?通常来说,一个好的切入点是“底线”。这可能会让我们关注定价和发票。但我们真正需要了解的是“领域愿景声明” 。以下是其中的一段摘录。
Now, what is the essence of the shipping model? Usually a good place to start looking is the “bottom line.” This might lead us to focus on pricing and invoices. But we really need to look at the DOMAIN VISION STATEMENT. Here is an excerpt from this one.
提高运营透明度,并提供工具以更快、更可靠地满足客户需求……
. . . Increase visibility of operations and provide tools to fulfill customer requirements faster and more reliably...
这个应用程序并非为销售部门设计,而是供公司一线操作人员使用。那么,我们开始吧。将所有与金钱相关的问题降格为辅助性问题(诚然,这些问题也很重要)。已经有人将其中一些项目放入了一个单独的软件包(账单)中。我们可以保留这个软件包,并进一步认识到它只是辅助性的。
This application is not being designed for the sales department. It is going to be used by the front-line operators of the company. So let’s relegate all money-related issues to (admittedly important) supporting roles. Someone has already placed some of these items into a separate package (Billing). We can keep that, and further recognize that it plays a supporting role.
重点应放在货物处理上:即根据客户要求交付货物。提取与这些活动最直接相关的类,即可在一个名为“交付”的新包中生成一个分离的核心,如图15.3所示。
The focus needs to be on the cargo handling: delivery of the cargo according to customer requirements. Extracting the classes most directly involved in these activities produces a SEGREGATED CORE in a new package called Delivery, as shown in Figure 15.3.
图 15.3. 按照客户要求可靠交付是本项目的核心目标。
Figure 15.3. Reliable delivery in adherence with customer requirements is the core goal of this project.
大部分课程都已迁移到新软件包中,但模型本身也发生了一些变化。
For the most part, classes have just moved into the new package, but there have been a few changes to the model itself.
首先,客户协议现在对处理步骤进行了约束。这是团队在分离核心组件时通常会获得的典型见解。随着关注点集中在高效、正确的交付上,很明显,客户协议中的交付约束至关重要,应该在模型中明确体现。
First, the Customer Agreement now constrains the Handling Step. This is typical of the insights that tend to arise as the team segregates the CORE. As attention is focused on effective, correct delivery, it becomes clear that the delivery constraints in the Customer Agreement are fundamental and should be explicit in the model.
另一项改动则更务实。在重构后的模型中,客户协议直接关联到货物,无需通过客户进行导航。(与客户一样,客户协议也需要在货物预订时关联。)在实际交付时,客户本身对运营的重要性远不及协议。在之前的模型中,需要根据客户在货运中扮演的角色找到正确的客户,然后查询其客户协议。这种交互会使模型描述变得冗长繁琐。新的关联方式使最重要的场景尽可能简单直接。现在,可以轻松地将客户从核心模型中完全移除。
The other change is more pragmatic. In the refactored model, the Customer Agreement is attached directly to the Cargo, rather than requiring a navigation through the Customer. (It will have to be attached when the Cargo is booked, just as the Customer is.) At actual delivery time, the Customer is not as relevant to operations as the agreement itself. In the other model, the correct Customer had to be found, according to the role it played in the shipment, and then queried for its Customer Agreement. This interaction would clog up every story you set out to tell about the model. The new association makes the most important scenarios as simple and direct as possible. Now it becomes easy to pull the Customer out of the CORE altogether.
那么,为什么要把客户(Customer)这个角色移除呢?重点在于满足客户的需求,所以客户角色乍一看似乎应该属于核心(CORE)范畴。然而,既然客户协议(Customer Agreement)可以直接使用,那么交付过程中的交互通常就不需要涉及到客户类了。而且,客户的基本模型也相当通用。
And what about pulling Customer out, anyway? The focus is on fulfilling the Customer’s requirements, so at first Customer seems to belong in the CORE. Yet the interactions during delivery do not usually need to involve the Customer class now that the Customer Agreement is available directly. And the basic model of a Customer is pretty generic.
有人可能会强烈主张将Leg保留在CORE 类中。我倾向于在CORE 类中保持精简,而Leg与Transport Schedule、Routing Service和Location 的关联性更强,而这三个类都不需要放在CORE 类中。但是,如果我想讲述的关于这个模型的故事中有很多都与Leg有关,我会将其移到Delivery类中,并忍受它与其他类分离带来的不便。
A strong argument could be made for Leg to remain in the CORE. I tend to be minimalist in the CORE, and the Leg has tighter cohesion with Transport Schedule, Routing Service, and Location, none of which needed to be in the CORE. But if a lot of the stories I wanted to tell about this model involved Legs, I’d move it into the Delivery package and suffer the awkwardness of its separation from those other classes.
在这个例子中,所有类的定义都和以前一样,但通常蒸馏需要重构类本身,以分离通用职责和特定领域的职责,然后才能将它们分离。
In this example, all the class definitions are the same as before, but often distillation requires refactoring the classes themselves to separate the generic and domain-specific responsibilities, which can then be segregated.
现在我们已经有了分离的核心,重构工作就完成了。但是剩下的打包内容只是“提取核心后剩余的所有内容”。我们可以继续进行其他重构,以获得更具沟通性的打包方式,如图15.4所示。
Now that we have a SEGREGATED CORE, the refactoring is complete. But the Shipping package we are left with is just “everything left over after we pulled out the CORE.” We can follow up with other refactorings to get more communicative packaging, as shown in Figure 15.4.
图 15.4.非核心子域的有意义的模块在分离核心完成后出现。
Figure 15.4. Meaningful MODULES for non-CORE subdomains follow after the SEGREGATED CORE is complete.
可能需要多次重构才能达到目前的状态;不必一次性完成。最终,我们得到了一个隔离的核心包、一个通用子域包和两个扮演辅助角色的特定领域包。更深入的分析最终可能会产生一个用于客户的通用子域包,或者最终可能会针对发货进行更专门化的处理。
It might take several refactorings to get to this point; it doesn’t have to be done all at once. Here, we’ve ended up with one SEGREGATED CORE package, one GENERIC SUBDOMAIN, and two domain-specific packages in supporting roles. Deeper insight might eventually produce a GENERIC SUBDOMAIN for Customer, or it might end up more specialized for shipping.
识别有用、有意义的模块是一种建模活动(如第 5 章所述)。开发人员和领域专家在知识整合过程中进行战略提炼方面的协作。
Recognizing useful, meaningful MODULES is a modeling activity (as discussed in Chapter 5). Developers and domain experts collaborate in strategic distillation as part of the knowledge crunching process.
即使是核心领域 模型通常包含很多细节,因此很难传达整体情况。
Even the CORE DOMAIN model usually has so much detail that communicating the big picture can be difficult.
我们通常通过将大型模型拆分成更小、更易于理解的子域,并将它们放置在单独的模块中来处理它们。这种简化式的打包方式通常能使复杂的模型变得易于管理。但有时,创建单独的模块反而会模糊甚至复杂化子域之间的交互。
We usually deal with a large model by breaking it into narrower subdomains that are small enough to be grasped and placing them in separate MODULES. This reductive style of packaging often works to make a complicated model manageable. But sometimes creating separate MODULES can obscure or even complicate the interactions between the subdomains.
当不同模块中的子域之间存在大量交互时,要么需要在模块之间创建许多引用,这会大大降低分区的价值;要么交互必须间接进行,这会使模型变得晦涩难懂。
When there is a lot of interaction between subdomains in separate MODULES, either many references will have to be created between MODULES, which defeats much of the value of the partitioning, or the interaction will have to be made indirect, which makes the model obscure.
考虑采用水平切片而非垂直切片。多态性使我们能够忽略抽象类型实例之间的许多细节差异。如果模块之间的大部分交互都可以用多态接口来表达,那么将这些类型重构到一个特殊的核心模块中可能就更有意义了。
Consider slicing horizontally rather than vertically. Polymorphism gives us the power to ignore a lot of the detailed variation among instances of an abstract type. If most of the interactions across MODULES can be expressed at the level of polymorphic interfaces, it may make sense to refactor these types into a special CORE MODULE.
我们并非在寻找技术技巧。只有当多态接口对应于领域中的基本概念时,这种技巧才真正有效。在这种情况下,分离这些抽象概念可以解耦模块,同时提炼出一个更小、更内聚的核心领域。
We are not looking for a technical trick here. This is a valuable technique only when the polymorphic interfaces correspond to fundamental concepts in the domain. In that case, separating these abstractions decouples the MODULES while distilling a smaller and more cohesive CORE DOMAIN.
识别模型中最基本的概念,并将它们分解为不同的类、抽象类或接口。设计这个抽象模型,使其能够表达重要组件之间的大部分交互。将这个抽象的总体模型放在它自己的模块中,而具体的、详细的实现类则保留在由子域定义的其他模块中。
Identify the most fundamental concepts in the model and factor them into distinct classes, abstract classes, or interfaces. Design this abstract model so that it expresses most of the interaction between significant components. Place this abstract overall model in its own MODULE, while the specialized, detailed implementation classes are left in their own MODULES defined by subdomain.
现在大多数专业课程都会引用抽象核心模块,但不会引用其他专业模块。抽象核心模块简明扼要地概述了主要概念及其相互作用。
Most of the specialized classes will now reference the ABSTRACT CORE MODULE but not the other specialized MODULES. The ABSTRACT CORE gives a succinct view of the main concepts and their interactions.
提取抽象核心的过程并非机械式的。例如,如果将所有在模块间频繁引用的类自动移到一个单独的模块中,结果很可能是一团毫无意义的混乱。对抽象核心进行建模需要对关键概念及其在系统主要交互中所扮演的角色有深刻的理解。换句话说,这是一种基于更深层次洞察的重构,通常需要大量的重新设计。
The process of factoring out the ABSTRACT CORE is not mechanical. For example, if all the classes that were frequently referenced across MODULES were automatically moved into a separate MODULE, the likely result would be a meaningless mess. Modeling an ABSTRACT CORE requires a deep understanding of the key concepts and the roles they play in the major interactions of the system. In other words, it is an example of refactoring to deeper insight. And it usually requires considerable redesign.
抽象核心最终应该与提炼文档非常相似(假设两者都用于同一个项目,并且提炼文档随着应用的深入而不断完善)。当然,抽象核心将以代码形式编写,因此会更加严谨和完整。
The ABSTRACT CORE should end up looking a lot like the distillation document (if both were used on the same project, and the distillation document had evolved with the application as insight deepened). Of course, the ABSTRACT CORE will be written in code, and therefore more rigorous and more complete.
蒸馏并非仅仅在粗略层面上将领域的各个部分从核心中分离出来。它还意味着通过持续重构来提炼这些子领域,尤其是核心领域,从而获得更深刻的洞察,最终构建出一个深度模型和灵活的设计。其目标是设计出一个模型清晰易懂、简洁明了地表达领域本质的模型。深度模型将领域中最关键的方面提炼成简单的元素,这些元素可以组合起来解决应用程序的重要问题。
Distillation does not operate only on the gross level of separating parts of the domain away from the CORE. It also means refining those subdomains, especially the CORE DOMAIN, through continuously refactoring toward deeper insight, driving toward a deep model and supple design. The goal is a design that makes the model obvious, a model that expresses the domain simply. A deep model distills the most essential aspects of a domain into simple elements that can be combined to solve the important problems of the application.
虽然在任何地方取得深度模型的突破都能带来价值,但在核心领域,它才能改变整个项目的轨迹。
Although a breakthrough to a deep model provides value anywhere it happens, it is in the CORE DOMAIN that it can change the trajectory of an entire project.
当你遇到一个设计糟糕的大型系统时,该从何入手?在极限编程(XP)社区,答案通常是以下两种情况之一:
When you encounter a large system that is poorly factored, where do you start? In the XP community, the answer tends to be either one of these:
1.从任何地方开始都行,因为所有代码都需要重构。
1. Just start anywhere, because it all has to be refactored.
2.从问题所在入手。我会重构必要的代码,以完成我的特定任务。
2. Start wherever it is hurting. I’ll refactor what I need to in order to get my specific task done.
我并不赞同这两种观点。第一种方法除了少数由顶尖程序员组成的项目外,几乎不切实际。第二种方法往往治标不治本,只处理表面症状而忽略根本原因,回避最棘手的问题。最终,代码重构会变得越来越困难。
I don’t hold with either of these. The first is impractical except in a few projects staffed entirely with top programmers. The second tends to pick around the edges, treating symptoms and ignoring root causes, shying away from the worst tangles. Eventually the code becomes harder and harder to refactor.
所以,如果你不能做到所有事情,也不能被痛苦所驱使,那你该怎么办?
So, if you can’t do it all, and you can’t be pain-driven, what do you do?
1.在以痛点为导向的重构中,首先要检查根源是否涉及核心域,或者核心域与某个支持元素之间的关系。如果是,那就果断地先解决这个问题。
1. In a pain-driven refactoring, you look to see if the root involves the CORE DOMAIN or the relationship of the CORE to a supporting element. If it does, you bite the bullet and fix that first.
2.当你有条件自由重构时,首先要专注于更好地分解核心域,改进核心的隔离,并将支持子域提纯为通用的。
2. When you have the luxury of refactoring freely, you focus first on better factoring of the CORE DOMAIN, on improving the segregation of the CORE, and on purifying supporting subdomains to be GENERIC.
这样才能让你的重构投入获得最大回报。
This is how to get the most bang for your refactoring buck.
成千上万的人独立创作了艾滋病纪念被。
Thousands of people worked independently to create the AIDS Quilt.
一家位于硅谷的小型设计公司受委托开发卫星通信系统模拟器。工作进展顺利。他们正在开发一种模型驱动设计,该设计能够表达和模拟各种网络状况和故障。
A small Silicon Valley design firm had been contracted to create a simulator for a satellite communications system. Work was progressing well. A MODEL-DRIVEN DESIGN was developing that could express and simulate a wide range of network conditions and failures.
但项目的主要开发人员感到不安。问题本身就很复杂。为了理清模型中错综复杂的关系,他们将设计分解成若干个易于管理的、连贯的模块。现在,模块的数量非常多。开发者应该在哪个包里查找特定功能?新类应该放在哪里?这些小包到底是什么意思?它们之间是如何协同工作的?而且还有更多东西需要构建。
But the lead developers on the project were uneasy. The problem was inherently complex. Driven by the need to clarify the intricate relationships in the model, they had decomposed the design into coherent MODULES of manageable size. Now there were a lot of MODULES. Which package should a developer look in to find a particular aspect of functionality? Where should a new class be placed? What did some of these little packages really mean? How did they all fit together? And there was still more to build.
开发人员之间沟通顺畅,每天都能找到解决办法,但项目负责人并不满足于现状,他们希望找到一种组织设计的方法,使其在迈向更高复杂度的过程中也能被理解和驾驭。
The developers communicated well with one another and could still figure out what to do from day to day, but the project leaders were not content to skirt the edge of comprehensibility. They wanted some way of organizing the design so that it could be understood and manipulated as it moved to the next level of complexity.
他们进行了头脑风暴,提出了许多可能性。他们还提出了其他的打包方案。或许可以提供一份系统概览文档,或者在建模工具中提供一些新的类图视图,引导开发人员找到正确的模块。但项目负责人并不满足于这些花招。
They brainstormed. There were a lot of possibilities. Alternative packaging schemes were proposed. Maybe some document could give an overview of the system, or some new views of the class diagram in the modeling tool could guide a developer to the right MODULE. But the project leaders weren’t satisfied with these gimmicks.
他们可以讲述一个简单的模拟故事,描述数据如何通过基础设施进行传输,以及多层电信技术如何确保数据的完整性和路由。模型中包含了故事的每一个细节,但故事的整体脉络却无法展现。
They could tell a simple story of their simulation, of the way data would be marshaled through an infrastructure, its integrity and routing assured by layers of telecommunications technology. Every detail of that story was in the model, yet the broad arc of the story could not be seen.
领域中一些关键概念缺失了。但这次并非对象模型中缺少一两个类,而是整个模型缺少某种结构。
Some essential concept from the domain was missing. But this time it was not a class or two missing from the object model, it was a missing structure for the model as a whole.
开发人员仔细思考这个问题一两周后,思路逐渐清晰。他们决定为设计构建一个结构。整个模拟器将被视为一系列与通信系统各个方面相关的层。最底层代表物理基础设施,即从一个节点向另一个节点传输比特的基本能力。接下来是数据包路由层,它负责处理特定数据流的路由问题。其他层则代表问题的其他概念层面,并分别阐述它们对系统的描述。
After the developers mulled over the problem for a week or two, the idea began to jell. They would impose a structure on the design. The entire simulator would be viewed as a series of layers related to aspects of the communications system. The bottom layer would represent the physical infrastructure, the basic ability to transmit bits from one node to another. Then there would be a packet-routing layer that brought together the concerns of how a particular data stream would be directed. Other layers would identify other conceptual levels of the problem. These layers would outline their story of the system.
他们着手重构代码,使其符合新的结构。模块(M ODULES)必须重新定义,以避免跨层。在某些情况下,对象职责也进行了重构,以确保每个对象都明确属于某一层。反之,在整个过程中,概念层本身的定义也根据实际应用经验进行了完善。这些层、模块(M ODULES)物体相互演化,最终,整个设计都遵循了这种层叠结构的轮廓。
They set out to refactor the code to conform to the new structure. MODULES had to be redefined so as not to span layers. In some cases, object responsibilities were refactored so that each object would clearly belong to one layer. Conversely, throughout this process the definitions of the conceptual layers themselves were refined based on the hands-on experience of applying them. The layers, MODULES, and objects coevolved until, in the end, the entire design followed the contours of this layered structure.
这些层并非代码中的模块或其他任何组成部分。它们是一套统领全局的规则,用于约束设计中任何特定模块或对象的边界和关系,即使是在与其他系统的接口处也是如此。
These layers were not MODULES or any other artifact in the code. They were an overarching set of rules that constrained the boundaries and relationships of any particular MODULE or object throughout the design, even at interfaces with other systems.
实施这一规则后,设计恢复到了易于理解的水平。人们大致知道在哪里可以找到特定功能。独立工作的个人也能做出彼此大致一致的设计决策。复杂性的上限被突破了。
Imposing this order brought the design back to comfortable intelligibility. People knew roughly where to look for a particular function. Individuals working independently could make design decisions that were broadly consistent with each other. The complexity ceiling had been lifted.
即使采用模块化设计,大型模型仍然可能过于复杂而难以理解。模块将设计分解成易于管理的小块,但模块数量可能很多。此外,模块化并不一定能带来设计的统一性。不同的对象、不同的封装之间,可能会采用各种各样的设计决策,每个决策都有其合理之处,但却各有特色。
Even with a MODULAR breakdown, a large model can be too complicated to grasp. The MODULES chunk the design into manageable bites, but there may be many of them. Also, modularity does not necessarily bring uniformity to the design. Object to object, package to package, a jumble of design decisions may be applied, each defensible but idiosyncratic.
BOUNDED CONTEXTS所施加的严格隔离可以防止腐败和混乱,但它本身并不能使人们更容易将系统视为一个整体。
The strict segregation imposed by BOUNDED CONTEXTS prevents corruption and confusion, but it does not, in itself, make it easier to see the system as a whole.
提炼确实有助于将注意力集中在核心领域,并将其他子领域视为辅助角色。但我们仍然需要理解这些辅助要素及其与核心领域以及彼此之间的关系。虽然理想情况下,核心领域应该清晰易懂,无需任何额外指导,但我们并非总能达到这种状态。
Distillation does help by focusing attention on the CORE DOMAIN and casting other subdomains in their supporting roles. But it is still necessary to understand the supporting elements and their relationships to the CORE DOMAIN—and to each other. And while the CORE DOMAIN would ideally be so clear and easily understood that no additional guidance would be needed, we are not always at that point.
在任何规模的项目中,人们都必须相对独立地负责系统的不同部分。缺乏协调或规则,就会出现风格各异、针对同一问题给出不同解决方案的混乱局面,导致难以理解各个部分如何协同运作,也无法把握全局。对设计某一部分的了解无法迁移到其他部分,最终项目会变成由不同模块的专家组成,他们各自领域之外无法互相帮助。持续集成失效,有限上下文也随之瓦解。
On a project of any size, people must work somewhat independently on different parts of the system. Without any coordination or rules, a confusion of different styles and distinct solutions to the same problems arises, making it hard to understand how the parts fit together and impossible to see the big picture. Learning about one part of the design will not transfer to other parts, so the project will end up with specialists in different MODULES who cannot help each other outside their narrow range. CONTINUOUS INTEGRATION breaks down and the BOUNDED CONTEXT fragments.
在一个缺乏统领全局原则的大型系统中,开发者往往无法根据元素在贯穿整个设计的模式中所扮演的角色来理解它们,从而导致只见树木不见森林。我们需要能够在不深入探究整体细节的情况下,理解单个部分在整体中的作用。
In a large system without any overarching principle that allows elements to be interpreted in terms of their role in patterns that span the whole design, developers cannot see the forest for the trees. We need to be able to understand the role of an individual part in the whole without delving into the details of the whole.
“大尺度结构”是一种语言,它能帮助你从宏观层面讨论和理解系统。一套高层次的概念或规则(或两者兼有)为整个系统建立了一种设计模式。这种组织原则既能指导设计,又能帮助理解。它有助于协调各个独立工作,因为大家对整体有一个共同的理解:各个部分如何共同作用,最终塑造整体。
A “large-scale structure” is a language that lets you discuss and understand the system in broad strokes. A set of high-level concepts or rules, or both, establishes a pattern of design for an entire system. This organizing principle can guide design as well as aid understanding. It helps coordinate independent work because there is a shared concept of the big picture: how the roles of various parts shape the whole.
设计一套规则或角色和关系模式,使其能够贯穿整个系统,并允许对每个部分在整体中的位置有所了解——即使不详细了解该部分的职责。
Devise a pattern of rules or roles and relationships that will span the entire system and that allows some understanding of each part’s place in the whole—even without detailed knowledge of the part’s responsibility.
结构可能局限于一个有限的上下文,但通常会跨越多个上下文,为项目中涉及的所有团队和子系统提供概念上的组织框架。良好的结构有助于深入理解模型,并能辅助提炼过程。
Structure may be confined to one BOUNDED CONTEXT but will usually span more than one, providing the conceptual organization to hold together all the teams and subsystems involved in the project. A good structure gives insight into the model and complements distillation.
大多数大型结构无法用 UML 表示,也无需如此。大多数大型结构塑造并解释模型和设计,但并不直接出现在模型和设计中。它们提供了一种关于设计的额外沟通方式。在本章的示例中,你会看到许多非正式的 UML 图,我在这些图上叠加了关于大型结构的信息。
You can’t represent most large-scale structures in UML, and you don’t need to. Most large-scale structures shape and explain the model and design but do not appear in it. They provide an extra level of communication about the design. In the examples of this chapter, you’ll see many informal UML diagrams on which I’ve superimposed information about the large-scale structure.
当团队规模较小且模型不太复杂时,将模型分解为命名良好的模块,进行一定程度的提炼,以及开发人员之间的非正式协调,就足以保持模型的组织性。
When a team is reasonably small and the model is not too complicated, decomposition into well-named MODULES, a certain amount of distillation, and informal coordination among developers can be sufficient to keep the model organized.
大型结构可以挽救一个项目,但不合适的结构则会严重阻碍项目进展。本章将探讨如何在这一层面上成功构建设计结构。
Large-scale structure can save a project, but an ill-fitting structure can severely hinder development. This chapter explores patterns for successfully structuring a design at this level.
图 16.1. 大尺度结构的一些模式
Figure 16.1. Some patterns of large-scale structure
许多开发者都体会过非结构化设计的弊端。为了避免混乱,项目通常会采用各种架构来限制开发。一些技术架构确实能解决技术问题,例如网络或数据持久化,但当架构开始涉足应用和领域模型领域时,它们本身也会带来问题。这些架构常常阻碍开发者创建能够有效解决特定问题的设计和模型。一些过于雄心勃勃的架构甚至会削弱应用开发者对编程语言本身的熟悉程度和技术能力。无论是技术架构还是领域架构,那些预先设定了大量设计决策的架构,都会随着需求的变化和理解的加深而变得束缚开发。
Many developers have experienced the cost of an unstructured design. To avoid anarchy, projects impose architectures that constrain development in various ways. Some technical architectures do solve technical problems, such as networking or data persistence, but when architectures start venturing into the arena of the application and domain model, they can create problems of their own. They often prevent the developers from creating designs and models that work well for the specifics of the problem. The most ambitious ones can even take away from application developers the familiarity and technical power of the programming language itself. And whether technical or domain oriented, architectures that freeze a lot of up-front design decisions can become a straitjacket as requirements change and as understanding deepens.
虽然一些技术架构(例如 J2EE)近年来已变得十分突出,但领域层的大规模结构却鲜有研究。不同应用的需求差异很大。
While some technical architectures (such as J2EE) have become prominent over the years, large-scale structure in the domain layer has not been explored much. Needs vary widely from one application to the next.
一开始就强行采用大规模架构很可能代价高昂。随着开发的进行,你几乎肯定会找到更合适的架构,甚至可能会发现,既定的架构反而限制了你采用能够极大程度地澄清或简化应用程序的设计方案。你或许可以利用部分架构,但却错失了良机。当你尝试各种变通方案或与架构师协商时,你的工作效率会大幅下降。但你的管理层认为架构已经完成。它原本是为了让应用程序更易于使用,那么为什么你不去开发应用程序,反而要处理这些架构问题呢?管理层和架构团队或许愿意听取意见,但如果每次修改都像是一场艰苦的战斗,那实在令人精疲力竭。
An up-front imposition of a large-scale structure is likely to be costly. As development proceeds, you will almost certainly find a more suitable structure, and you may even find that the prescribed structure is prohibiting you from taking a design route that would greatly clarify or simplify the application. You may be able to use some of the structure, but you’re forgoing opportunities. Your work slows down as you try workarounds or try to negotiate with the architects. But your managers think the architecture is done. It was supposed to make this application easy, so why aren’t you working on the application instead of dealing with all these architecture problems? The managers and architecture teams may even be open to input, but if each change is a heroic battle, it is too exhausting.
设计上的自由放任会导致系统整体难以理解,维护起来也极其困难。但架构设计也可能用预先设定的设计假设束缚项目,剥夺应用程序特定部分开发人员/设计人员的过多权力。很快,开发人员要么会为了适应架构而简化应用程序,要么会彻底破坏架构,最终导致开发缺乏协调的问题再次出现。
Design free-for-alls produce systems no one can make sense of as a whole, and they are very difficult to maintain. But architectures can straitjacket a project with up-front design assumptions and take too much power away from the developers/designers of particular parts of the application. Soon, developers will dumb down the application to fit the structure, or they will subvert it and have no structure at all, bringing back the problems of uncoordinated development.
问题不在于是否存在指导规则,而在于这些规则的僵化程度及其来源。如果设计规则真正契合实际情况,它们不仅不会成为阻碍,反而会推动开发朝着有益的方向发展,并确保一致性。
The problem is not the existence of guiding rules, but rather the rigidity and source of those rules. If the rules governing the design really fit the circumstances, they will not get in the way but actually push development in a helpful direction, as well as provide consistency.
所以:
Therefore:
让这个概念性的大型结构随着应用而演进,甚至可能在此过程中转变为完全不同的结构类型。不要过度限制必须在掌握详尽信息的基础上做出的详细设计和模型决策。
Let this conceptual large-scale structure evolve with the application, possibly changing to a completely different type of structure along the way. Don’t overconstrain the detailed design and model decisions that must be made with detailed knowledge.
各个部分都有其自然或有效的组织和表达方式,但这些方式可能并不适用于整体,因此强加全局规则会降低这些部分的理想性。选择使用大规模结构优先考虑的是模型的整体可管理性,而非各个部分的最优结构。因此,在统一结构和以最自然的方式表达各个组件之间必然存在某种妥协。可以通过基于实际经验和领域知识选择结构,并避免过于限制性的结构来缓解这种妥协。结构与领域和需求的高度契合实际上可以简化详细的建模和设计,因为它有助于快速排除许多选项。
Individual parts have natural or useful ways of being organized and expressed that may not apply to the whole, so imposing global rules makes these parts less ideal. Choosing to use a large-scale structure favors manageability of the model as a whole over optimal structuring of the individual parts. Therefore, there will be some compromise between unifying structure and freedom to express individual components in the most natural way. This can be mitigated by selecting the structure based on actual experience and knowledge of the domain and by avoiding over-constrictive structures. A really nice fit of structure to domain and requirements actually makes detailed modeling and design easier, by helping to quickly eliminate a lot of options.
这种结构还能为设计决策提供捷径,这些决策原则上可以通过逐个对象进行处理来找到,但实际上会耗费太多时间且结果不一致。当然,持续重构仍然是必要的,但这会让重构过程更易于管理,并有助于不同的人提出一致的解决方案。
The structure can also give shortcuts to design decisions that could, in principle, be found by working on the individual object level, but would, in practice, take too long and have inconsistent results. Of course, continuous refactoring is still necessary, but this will make it a more manageable process and can help make different people come up with consistent solutions.
大型架构通常需要适用于有界上下文。在实际项目的迭代过程中,架构会逐渐失去那些将其紧密绑定到特定模型的特性,并演化出与领域概念轮廓相对应的特性。这并不意味着它对模型没有任何假设,而是意味着它不会将针对特定局部情况量身定制的理念强加于整个项目。它必须允许处于不同上下文中的开发团队根据自身需求对模型进行调整。
A large-scale structure generally needs to be applicable across BOUNDED CONTEXTS. Through iteration on a real project, a structure will lose features that tightly bind it to a particular model and evolve features that correspond to CONCEPTUAL CONTOURS of the domain. This doesn’t mean that it will have no assumptions about the model, but it will not impose upon the entire project ideas tailored to a particular local situation. It has to leave freedom for development teams in distinct CONTEXTS to vary the model in ways that address their local needs.
此外,大型建筑必须适应开发方面的实际限制。例如,设计者可能无法控制……对于系统某些部分的模型,尤其是在涉及外部或遗留子系统的情况下,可能会出现问题。可以通过修改结构以更好地适应特定的外部元素来解决。也可以通过明确应用程序与外部组件的交互方式来解决。还可以通过使结构足够灵活以应对各种棘手的实际情况来解决。
Also, large-scale structures must accommodate practical constraints on development. For example, designers may have no control over the model of some parts of the system, especially in the case of external or legacy subsystems. This could be handled by changing the structure to better fit the specific external elements. It could be handled by specifying ways in which the application relates to externals. It might be handled by making the structure loose enough to flex around awkward realities.
与上下文图不同,大规模结构是可选的。只有当成本效益比有利,且找到合适的结构时,才应采用大规模结构。事实上,对于足够简单、可以通过分解成模块来理解的系统,大规模结构并非必要。只有当找到一种能够极大地阐明系统,且不会对模型开发施加不自然限制的结构时,才应采用大规模结构。由于不合适的结构比没有结构更糟糕,因此最好不要追求全面性,而是找到一个能够解决已出现问题的最小集合。少即是多。
Unlike the CONTEXT MAP, a large-scale structure is optional. One should be imposed when costs and benefits favor it, and when a fitting structure is found. In fact, it is not needed for systems that are simple enough to be understood when broken into MODULES. Large-scale structure should be applied when a structure can be found that greatly clarifies the system without forcing unnatural constraints on model development. Because an ill-fitting structure is worse than none, it is best not to shoot for comprehensiveness, but rather to find a minimal set that solves the problems that have emerged. Less is more.
大型结构虽然非常有用,但也可能存在一些例外情况。然而,这些例外情况需要以某种方式加以标记,以便开发人员可以假定结构得到遵循,除非另有说明。如果例外情况开始增多,则需要更改或放弃该结构。
A large-scale structure can be very helpful and still have a few exceptions, but those exceptions need to be flagged somehow, so that developers can assume the structure is being followed unless otherwise noted. And if those exceptions start to get numerous, the structure needs to be changed or discarded.
如前所述,构建一个既能赋予开发者必要自由又能避免混乱的架构绝非易事。尽管软件系统的技术架构方面已有大量研究成果,但关于领域层架构的文献却寥寥无几。一些方法会削弱面向对象范式,例如按应用任务或用例划分领域的方法。整个领域仍处于发展初期。我观察到一些在各种项目中涌现的大型架构的通用模式。本章将讨论其中四种。这些模式或许能满足您的需求,或者为您量身定制的项目架构提供思路。
As mentioned, it is no mean feat to create a structure that gives the necessary freedom to developers while still averting chaos. Although a lot of work has been done on technical architecture for software systems, little has been published on the structuring of the domain layer. Some approaches weaken the object-oriented paradigm, such as those that break down the domain by application task or by use case. This whole area is still undeveloped. I’ve observed a few general patterns of large-scale structures that have emerged on various projects. I’ll discuss four in this chapter. One of these may fit your needs or lead to ideas for a structure tailored to your project.
隐喻思维在软件开发中非常普遍,尤其是在模型构建方面。但在极限编程实践中,“隐喻”一词特指一种运用隐喻来规范整个系统开发的方法。
Metaphorical thinking is pervasive in software development, especially with models. But the Extreme Programming practice of “metaphor” has come to mean a particular way of using a metaphor to bring order to the development of a whole system.
就像防火墙可以保护建筑物免受邻近建筑物火灾的侵袭一样,软件“防火墙”可以保护本地网络免受外部大型网络的威胁。这种比喻影响了网络架构,并催生了一个完整的产品类别。市面上有很多相互竞争的防火墙产品——它们各自独立开发,但被认为具有一定的互换性——可供消费者选择。即使是网络新手也能轻松理解这个概念。这种在整个行业和客户群体中普遍存在的理解,很大程度上要归功于这种比喻。
Just as a firewall can save a building from a fire raging through neighboring buildings, a software “firewall” protects the local network from the dangers of the larger networks outside. This metaphor has influenced network architectures and shaped a whole product category. Multiple competing firewalls—developed independently, understood to be somewhat interchangeable—are available for consumers. Novices to networking readily grasp the concept. This shared understanding throughout the industry and among customers is due in no small part to the metaphor.
然而,这种类比并不精确,而且其效力是一把双刃剑。使用防火墙比喻导致了一些软件屏障的开发,这些屏障有时选择性不足,阻碍了必要的通信,同时又无法抵御来自防火墙内部的威胁。例如,无线局域网就很容易受到攻击。防火墙的清晰性固然是一大优势,但所有比喻都带有其自身的局限性。
Yet it is an inexact analogy, and its power cuts both ways. The use of the firewall metaphor has led to development of software barriers that are sometimes insufficiently selective and impede desirable exchanges, while offering no protection against threats originating within the wall. Wireless LANs, for example, are vulnerable. The clarity of the firewall has been a boon, but all metaphors carry baggage.1
软件设计往往非常抽象,难以理解。开发人员和用户都需要切实可行的方法来理解系统,并对系统形成整体认知。
Software designs tend to be very abstract and hard to grasp. Developers and users alike need tangible ways to understand the system and share a view of the system as a whole.
从某种程度上说,隐喻深深植根于我们的思维方式中,渗透到每一个设计之中。系统拥有彼此“叠加”的“层级”,它们的“中心”也各具“内核”。但有时,一个恰当的隐喻能够传达整个设计的核心主题,并为所有团队成员提供共识。
On one level, metaphor runs so deeply in the way we think that it pervades every design. Systems have “layers” that “lay on top” of each other. They have “kernels” at their “centers.” But sometimes a metaphor comes along that can convey the central theme of a whole design and provide a shared understanding among all team members.
当这种情况发生时,系统实际上会受到隐喻的影响。开发者会做出与系统隐喻相一致的设计决策。这种一致性将使其他开发者能够用相同的隐喻来解读复杂系统的各个组成部分。比喻。开发人员和专家在讨论中会有一个比模型本身更具体的参照点。
When this happens, the system is actually shaped by the metaphor. A developer will make design decisions consistent with the system metaphor. This consistency will enable other developers to interpret the many parts of a complex system in terms of the same metaphor. The developers and experts have a reference point in discussions that may be more concrete than the model itself.
系统隐喻是一种宽松、易于理解的大规模结构,它与对象范式相协调。由于系统隐喻本身只是对领域的一种类比,不同的模型可以近似地映射到它上面,这使得它可以应用于多个限定情境,并有助于协调它们之间的工作。
A SYSTEM METAPHOR is a loose, easily understood, large-scale structure that it is harmonious with the object paradigm. Because the SYSTEM METAPHOR is only an analogy to the domain anyway, different models can map to it in an approximate way, which allows it to be applied in multiple BOUNDED CONTEXTS, helping to coordinate work between them.
系统隐喻之所以成为一种流行的方法,是因为它是极限编程(XP)的核心实践之一(Beck 2000)。然而,真正有效的系统隐喻项目却寥寥无几,而且人们还试图将这种理念强加到一些适得其反的领域。一个具有说服力的隐喻会带来这样的风险:设计可能会借鉴一些与当前问题不符的类比,或者说,虽然这个类比很有吸引力,但可能并不恰当。
SYSTEM METAPHOR has become a popular approach because it is one of the core practices of Extreme Programming (Beck 2000). Unfortunately, few projects have found really useful METAPHORS, and people have tried to push the idea into domains where it is counterproductive. A persuasive metaphor introduces the risk that the design will take on aspects of the analogy that are not desirable for the problem at hand, or that the analogy, while seductive, may not be apt.
也就是说,系统隐喻是一种众所周知的大规模结构形式,在某些项目中很有用,它很好地说明了结构的一般概念。
That said, SYSTEM METAPHOR is a well-known form of large-scale structure that is useful on some projects, and it nicely illustrates the general concept of a structure.
所以:
Therefore:
当一个具体的系统类比出现,能够激发团队成员的想象力,并引导思考朝着有益的方向发展时,就将其作为一种宏观结构来采用。围绕这个隐喻来组织设计,并将其融入通用语言中。系统隐喻既应促进关于系统的沟通,也应指导系统的开发。这可以提高系统不同部分之间的一致性,甚至有可能跨越不同的有限上下文。但由于所有隐喻都存在不精确性,因此需要不断重新审视隐喻,检查其是否过度延伸或不恰当,如果它阻碍了工作,就应该随时放弃它。
When a concrete analogy to the system emerges that captures the imagination of team members and seems to lead thinking in a useful direction, adopt it as a large-scale structure. Organize the design around this metaphor and absorb it into the UBIQUITOUS LANGUAGE. The SYSTEM METAPHOR should both facilitate communication about the system and guide development of it. This increases consistency in different parts of the system, potentially even across different BOUNDED CONTEXTS. But because all metaphors are inexact, continually reexamine the metaphor for overextension or inaptness, and be ready to drop it if it gets in the way.
因为大多数项目中都没有出现有用的隐喻,所以极限编程 (XP) 社区中的一些人开始谈论朴素的隐喻,他们指的是领域模型本身。
Because a useful metaphor doesn’t present itself on most projects, some in the XP community have come to talk of the naive metaphor, by which they mean the domain model itself.
这个术语的一个问题在于,成熟的领域模型绝非天真无知。事实上,“薪资处理就像一条装配线”。与经过与领域专家多次知识研讨的迭代模型相比,这种观点可能显得幼稚得多,而且这种模型已经通过与实际应用程序的紧密结合而得到验证。
One trouble with this term is that a mature domain model is anything but naive. In fact, “payroll processing is like an assembly line” is likely a much more naive view than a model that is the product of many iterations of knowledge crunching with domain experts, and that has been proven by being tightly woven into the implementation of a working application.
“幼稚隐喻”这个术语应该弃用。
The term naive metaphor should be retired.
系统隐喻并非适用于所有项目。大规模结构通常并非必要。在极限编程的十二项实践中,系统隐喻的角色可以由普适语言来扮演。项目应在找到合适的系统隐喻或其他大规模结构时,将其添加到普适语言中。
SYSTEM METAPHORS are not useful on all projects. Large-scale structure in general is not essential. In the 12 practices of Extreme Programming, the role of a SYSTEM METAPHOR could be fulfilled by a UBIQUITOUS LANGUAGE. Projects should augment that LANGUAGE with SYSTEM METAPHORS or other large-scale structures when they find one that fits well.
本书中,每个对象都被赋予了特定的、相关的职责。这种职责驱动的设计方法也适用于更大规模的项目。
Throughout this book, individual objects have been assigned narrow sets of related responsibilities. Responsibility-driven design also applies to larger scales.
当每个对象都拥有各自独立的职责时,就缺乏指导原则、统一性,也无法协同处理领域内的大部分事务。为了使大型模型更具连贯性,对这些职责的分配施加一定的结构化约束是很有必要的。
When each individual object has handcrafted responsibilities, there are no guidelines, no uniformity, and no ability to handle large swaths of the domain together. To give coherence to a large model, it is useful to impose some structure on the assignment of those responsibilities.
当你对某个领域有了深入的了解,一些宏观模式就会逐渐显现。有些领域本身就具有天然的分层结构。某些概念和活动是在其他元素的背景下发生的,这些元素由于各种原因独立变化,且变化速度各不相同。我们如何才能利用这种天然结构,使其更加清晰可见、更加实用呢?这种分层结构暗示了层次化,而层次化正是最成功的建筑设计模式之一(参见Buschmann 等人,1996 年的研究)。
When you gain a deep understanding of a domain, broad patterns start to become visible. Some domains have a natural stratification. Certain concepts and activities take place against a background of other elements that change independently and at a different rate for different reasons. How can we take advantage of this natural structure, make it more visible and useful? This stratification suggests layering, one of the most successful architectural design patterns (Buschmann et al. 1996, among others).
层是系统的划分,每个划分中的成员都能感知并使用“下层”的服务,但对“上层”的服务一无所知,也独立于“上层”之外。绘制模块依赖关系图时,通常会这样排列:一个模块如果存在依赖项,则该模块会位于其依赖项的下方。这样一来,层有时会自行调整,使得较低层级的对象在概念上不依赖于较高层级的对象。
Layers are partitions of a system in which the members of each partition are aware of and are able to use the services of the layers “below,” but unaware of and independent of the layers “above.” When the dependencies of MODULES are drawn, they are often laid out so that a MODULE with dependents appears below its dependents. In this way, layers sometimes sort themselves out so that none of the objects in the lower levels is conceptually dependent on those in higher layers.
但这种临时性的分层方法虽然可以简化依赖关系追踪,有时也符合直觉,却无法提供太多关于模型的洞察,也无法指导建模决策。我们需要更有针对性的方法。
But this ad hoc layering, while it can make tracing dependencies easier—and sometimes makes some intuitive sense—doesn’t give much insight into the model or guide modeling decisions. We need something more intentional.
图 16.2. 临时分层:这些包是做什么用的?
Figure 16.2. Ad hoc layering: What are these packages about?
在具有自然分层的模型中,可以围绕主要职责定义概念层,将分层和职责驱动设计这两个强大的原则结合起来。
In a model with a natural stratification, conceptual layers can be defined around major responsibilities, uniting the two powerful principles of layering and responsibility-driven design.
这些职责必须比通常分配给单个对象的职责范围更广,稍后将通过示例进行说明。在设计各个模块和聚合时,我们会对其进行分解,使其始终处于这些主要职责的范围内。这种职责的命名分组本身就能增强模块化系统的可理解性,因为模块的职责更容易理解。而将高层职责与分层结构相结合,则为我们提供了一个系统的组织原则。
These responsibilities must be considerably broader than those typically assigned to individual objects, as examples will illustrate shortly. As individual MODULES and AGGREGATES are designed, they are factored to keep them within the bounds of one of these major responsibilities. This named grouping of responsibilities by itself could enhance the comprehensibility of a modularized system, since the responsibilities of MODULES could be more readily interpreted. But combining high-level responsibilities with layering gives us an organizing principle for a system.
所以:
Therefore:
审视模型中的概念依赖关系,以及领域内不同部分变化的速度和来源。如果识别出领域中的自然分层,请将其转化为宽泛的抽象职责。这些职责应清晰地阐述系统的高层目标和设计。重构模型,使每个领域对象、聚合(AGGREGATE)和模块(MODULE)的职责都能恰好归属于同一层级。
Look at the conceptual dependencies in your model and the varying rates and sources of change of different parts of your domain. If you identify natural strata in the domain, cast them as broad abstract responsibilities. These responsibilities should tell a story of the high-level purpose and design of your system. Refactor the model so that the responsibilities of each domain object, AGGREGATE, and MODULE fit neatly within the responsibility of one layer.
这是一个相当抽象的描述,但通过几个例子就会变得清晰明了。卫星通信模拟器故事以层层递进的方式开启了这一章节,阐述了责任划分。我曾在制造控制和财务管理等各个领域看到过责任层级的有效运用。
This is a pretty abstract description, but it will become clear with a few examples. The satellite communications simulator whose story opened this chapter layered its responsibility. I have seen RESPONSIBILITY LAYERS used to good effect in domains as various as manufacturing control and financial management.
以下示例详细探讨了责任层,以感受发现任何类型的大规模结构,以及它如何指导和约束建模和设计。
The following example explores RESPONSIBILITY LAYERS in detail to give a feel for the discovery of a large-scale structure of any sort, and the way it guides and constrains modeling and design.
让我们来看看将责任层应用于前几章示例中讨论的货物运输应用会产生哪些影响。
Let’s look at the implications of applying RESPONSIBILITY LAYERS to the cargo shipping application discussed in the examples of previous chapters.
故事继续,团队在创建模型驱动设计和提炼核心领域方面取得了显著进展。但随着设计的逐步完善,他们发现如何协调各个部分之间的衔接存在问题。他们正在寻找一种能够凸显系统核心主题并确保所有成员步调一致的大型架构。
As we rejoin the story, the team has made considerable progress creating a MODEL-DRIVEN DESIGN and distilling a CORE DOMAIN. But as the design fleshes out, they are having trouble coordinating how all the parts fit together. They are looking for a large-scale structure that can bring out the main themes of their system and keep everyone on the same page.
下面展示的是该模型的一个代表性部分。
Here is a look at a representative part of the model.
图 16.3. 货物运输路径的基本运输领域模型
Figure 16.3. A basic shipping domain model for routing cargoes
图 16.4. 在预订过程中使用该模型规划货物路线
Figure 16.4. Using the model to route a cargo during booking
团队成员已在航运领域深耕数月,并注意到该领域概念存在一些天然的层次结构。讨论运输时刻表(船舶和火车的预定航程)时,完全可以忽略运输工具上的货物。但如果不提及运输工具,就很难讨论货物的追踪。概念上的依存关系非常清晰。团队可以很容易地区分出两个层次:“运营”以及支撑这些运营的基础,他们称之为“能力”。
The team members have been steeped in the domain of shipping for months, and they have noticed some natural stratification of its concepts. It is quite reasonable to discuss transport schedules (the scheduled voyages of ships and trains) without referring to the cargoes aboard those transports. It is harder to talk about tracking a cargo without referring to the transport carrying it. The conceptual dependencies are pretty clear. The team can readily distinguish two layers: “Operations” and the substrate of those operations, which they dub “Capability.”
公司过去、现在和计划中的各项活动都汇集到运营层。运营层中最显而易见的对象是货物,它是公司日常运营的核心。路线规范是货物的组成部分,用于指示交付要求。行程安排则是具体的交付计划。这两个对象都属于货物的 聚合体,它们的生命周期与当前交付的时间范围紧密相关。
Activities of the company, past, current, and planned, are collected into the Operations layer. The most obvious Operations object is Cargo, which is the focus of most of the day-to-day activity of the company. The Route Specification is an integral part of Cargo, indicating delivery requirements. The Itinerary is the operational delivery plan. Both of these objects are part of the Cargo’s AGGREGATE, and their life cycles are tied to the time frame of an active delivery.
这一层反映了公司为开展运营而动用的资源。过境航段就是一个典型的例子。船舶已安排运行,并具有一定的载货能力,但可能无法完全利用。
This layer reflects the resources the company draws upon in order to carry out operations. The Transit Leg is a classic example. The ships are scheduled to run and have a certain capacity to carry cargo, which may or may not be fully utilized.
没错,如果我们专注于运营船队,那么中转航段就应该放在运营层。但这个系统的用户并不担心这个问题。(如果公司同时参与这两项业务,并且希望协调这两项业务,那么开发团队可能需要考虑不同的分层方案,例如采用两个独立的层,比如“运输运营”和“货物运营”。)
True, if we were focused on operating a shipping fleet, Transit Leg would be in the Operations layer. But the users of this system aren’t worried about that problem. (If the company were involved in both those activities and wanted the two coordinated, the development team might have to consider a different layering scheme, perhaps with two distinct layers, such as “Transport Operations” and “Cargo Operations.”)
更棘手的决策是如何定位“客户”这一概念。在某些行业,客户往往具有短暂性:他们在包裹递送期间引人注目,之后便被遗忘,直到下次再次光顾。对于面向个人消费者的包裹递送服务而言,这种特性使得客户仅仅成为运营层面的考量因素。但我们假设的这家物流公司倾向于与客户建立长期关系,并且大部分业务都来自回头客。鉴于业务用户的这种意图,“客户”应该被置于潜在层。正如您所见,这并非一个技术性的决策,而是为了捕捉和传达领域知识而做出的尝试。
A trickier decision is where to place Customer. In some businesses, customers tend to be transient: they’re interesting while a package is being delivered and then mostly forgotten until next time. This quality would make customers only an operational concern for a parcel delivery service aimed at individual consumers. But our hypothetical shipping company tends to cultivate long-term relationships with customers, and most work comes from repeat business. Given these intentions of the business users, the Customer belongs in the potential layer. As you can see, this was not a technical decision. It was an attempt to capture and communicate knowledge of the domain.
由于货物与客户之间的关联只能单向遍历,货物 存储库需要一个查询来查找特定客户的所有货物。当初这样设计是有充分理由的,但随着大规模架构的实施,这现在已成为一项必要要求。
Because the association between Cargo and Customer can be traversed in only one direction, the Cargo REPOSITORY will need a query that finds all Cargoes for a particular Customer. There were good reasons to design it that way anyway, but with the imposition of the large-scale structure, it is now a requirement.
图 16.5. 查询替换了违反分层的双向关联。
Figure 16.5. A query replaces a bidirectional association that violates the layering.
图 16.6. 初次分层模型
Figure 16.6. A first-pass layered model
虽然“运营”和“能力”之间的区别使情况更加清晰,但组织架构仍在不断演变。经过几周的试验,团队发现了另一个关键区别。在大多数情况下,初始的两个层级都侧重于处理当前的实际情况或计划。但是,路由(以及本例中未提及的许多其他元素)并不属于当前的实际运营情况或计划。它有助于制定关于变更这些计划的决策。因此,团队定义了一个新的层级,负责“决策支持”。
While the distinction between Operations and Capability clarifies the picture, order continues to evolve. After a few weeks of experimentation, the team zeroes in on another distinction. For the most part, both initial layers focus on situations or plans as they are. But the Router (and many other elements excluded from this example) isn’t part of current operational realities or plans. It helps make decisions about changing those plans. The team defines a new layer responsible for “Decision Support.”
该软件层为用户提供规划和决策工具,并且有可能自动执行一些决策(例如,当运输计划发生变化时自动重新安排货物路线)。
This layer of the software provides the user with tools for planning and decision making, and it could potentially automate some decisions (such as automatically rerouting Cargoes when a transport schedule changes).
路由工具是一项服务,它可以帮助订舱代理选择最佳的货物运输方式。因此,路由工具完全属于决策支持范畴。
The Router is a SERVICE that helps a booking agent choose the best way to send a Cargo. This places the Router squarely in Decision Support.
此模型中的所有引用都与三层结构一致,只有一个不一致之处:运输段上的“优先”属性。该属性的存在是因为公司倾向于尽可能使用自有船舶,或使用与其签订优惠合同的其他公司的船舶。“优先”属性用于引导路由程序优先选择这些优先运输方式。此属性与“能力”无关,而是一项指导决策的策略。要使用新的责任层,需要重构此模型。
The references within this model are all consistent with the three layers except for one discordant element: the “is preferred” attribute on Transport Leg. This attribute exists because the company prefers to use its own ships when it can, or the ships of certain other companies with which it has favorable contracts. The “is preferred” attribute is used to bias the Router toward these favored transports. This attribute has nothing to do with “Capability.” It is a policy that directs decision making. To use the new RESPONSIBILITY LAYERS, the model will have to be refactored.
图 16.7. 重构模型以符合新的分层结构
Figure 16.7. Refactoring the model to conform to the new layering structure
这种分解使得路线偏好策略更加明确,同时使运输段更加聚焦于运输能力的基本概念。基于对领域的深刻理解而构建的大规模模型,通常会推动模型朝着更清晰的方向发展,从而阐明其含义。
This factoring makes the Route Bias Policy more explicit while making Transport Leg more focused on the fundamental concept of transportation capability. A large-scale structure based on a deep understanding of the domain will often push the model in directions that clarify its meaning.
这种新模型现在可以完美地融入到大规模的结构中。
This new model now smoothly fits into the large-scale structure.
图 16.8. 重构和重新构造的模型
Figure 16.8. The restructured and refactored model
熟悉所选分层结构的开发人员可以更容易地辨别各个部分的角色和依赖关系。随着复杂性的增加,大规模结构的价值也随之增加。
A developer accustomed to the chosen layers can more readily discern the roles and dependencies of the parts. The value of the large-scale structure increases as the complexity grows.
请注意,虽然我用修改后的 UML 图来说明这个例子,但该图只是为了说明分层结构。UML 标准中并没有这种表示法,所以这是为了方便读者理解而添加的额外信息。如果代码是项目的最终设计文档,那么拥有一个可以按层浏览类或者至少按层报告类的工具将会很有帮助。
Note that although I’m illustrating this example with a modified UML diagram, the drawing is just a way of communicating the layering. UML doesn’t include this notation, so this is additional information imposed for the sake of the reader. If code is the ultimate design document for your project, it would be helpful to have a tool for browsing classes by layer or at least for reporting them by layer.
一旦采用大规模架构,后续的建模和设计决策都必须将其纳入考虑。举例来说,假设我们需要在这个已有的分层设计中添加一项新功能。领域专家刚刚告知我们,某些类别的危险品需要遵守运输路线限制。某些物品可能不允许通过某些运输方式或进入某些港口。我们必须确保路由机制符合这些规定。
Once a large-scale structure has been adopted, subsequent modeling and design decisions must take it into account. To illustrate, suppose that we must add a new feature to this already layered design. The domain experts have just told us that routing restrictions apply for certain categories of hazardous materials. Certain materials may not be allowed on some transports or in some ports. We have to make the Router obey these regulations.
有很多可能的方法。在缺乏大规模结构的情况下,一个有吸引力的设计是将这些路由规则纳入其中的责任交给拥有路线规范和危险材料(HazMat)代码的对象——即货物。
There are many possible approaches. In the absence of a large-scale structure, one appealing design would be to give the responsibility of incorporating these routing rules to the object that owns the Route Specification and the Hazardous Material (HazMat) code—namely the Cargo.
图 16.9. 危险货物运输路线的可能设计方案
Figure 16.9. A possible design for routing hazardous cargo
图 16.10
Figure 16.10
问题在于,这种设计并不适合大规模架构。危险品运输路线策略服务本身没有问题;它完全符合决策支持层的职责范围。问题在于货物(一个运营对象)对危险品运输路线策略服务(一个决策支持对象)的依赖关系。只要项目采用这些层级结构,就不能允许这种模型。它会让期望遵循既定架构的开发人员感到困惑。
The trouble is, this design doesn’t fit the large-scale structure. The HazMat Route Policy Service is not the problem; it fits neatly into the responsibility of the Decision Support layer. The problem is the dependency of Cargo (an Operational object) on HazMat Route Policy Service (a Decision Support object). As long as the project is committed to these layers, this model cannot be allowed. It would confuse developers who expected the structure to be followed.
设计方案总是有很多种,我们只需要选择另一种——一种符合大规模架构规则的方案。危险品路线策略服务本身没问题,但我们需要转移策略的使用职责。不妨尝试让路由器在搜索路线之前负责收集相应的策略。这意味着需要修改路由器的接口,使其包含策略可能依赖的对象。以下是一种可能的设计方案。
There are always many design possibilities, and we’ll just have to choose another one—one that follows the rules of the large-scale structure. The HazMat Route Policy Service is all right, but we need to move the responsibility for using the policy. Let’s try giving the Router the responsibility for collecting appropriate policies before searching for a route. This means changing the Router interface to include objects that policies might depend on. Here is a possible design.
图 16.11. 与分层一致的设计
Figure 16.11. A design consistent with layering
下一页的图 16.12显示了典型的交互。
A typical interaction is shown in Figure 16.12 on the next page.
图 16.12
Figure 16.12
当然,这并不一定比另一个设计更好。两者各有利弊。但如果项目中的每个人都能以一致的方式做出决策,那么整体设计就会更容易理解,而这值得我们在细节设计选择上做出一些适度的妥协。
Now, this isn’t necessarily a better design than the other. They both have pros and cons. But if everyone on a project makes decisions in a consistent way, the design as a whole will be much more comprehensible, and that is worth some modest trade-offs on detailed design choices.
如果结构迫使人们做出许多尴尬的设计选择,那么按照不断演化的秩序,应该对其进行评估,并可能需要修改甚至放弃。
If the structure is forcing many awkward design choices, then in keeping with EVOLVING ORDER, it should be evaluated and perhaps modified or even discarded.
找到合适的责任层级,或者任何大规模的组织结构,关键在于理解问题领域并进行试验。如果允许组织结构不断演进,那么初始起点并非至关重要,但糟糕的选择确实会增加工作量。组织结构很可能最终面目全非。因此,本文建议的指导原则不仅适用于从零开始构建组织结构,也适用于考虑组织结构的转型。
Finding good RESPONSIBILITY LAYERS, or any large-scale structure, is a matter of understanding the problem domain and experimenting. If you allow EVOLVING ORDER, the initial starting point is not critical, although a poor choice does add work. The structure may well evolve into something unrecognizable. So the guidelines suggested here should be applied when considering transformations of the structure as much as when choosing from scratch.
当图层被替换、合并、拆分和重新定义时,以下是一些需要查找和保留的有用特征。
As layers get switched out, merged, split, and redefined, here are some useful characteristics to look for and preserve.
•叙事性。各个层级应传达领域的基本现实或优先事项。选择大规模架构与其说是技术决策,不如说是业务建模决策。各个层级应凸显业务的优先事项。
• Storytelling. The layers should communicate the basic realities or priorities of the domain. Choosing a large-scale structure is less a technical decision than a business modeling decision. The layers should bring out the priorities of the business.
•概念依赖性。“上层”概念应在“下层”概念的背景下才有意义,而“下层”概念本身也应有意义。
• Conceptual dependency. The concepts in the “upper” layers should have meaning against the backdrop of the “lower” layers, while the lower-layer concepts should be meaningful standing alone.
•概念轮廓。如果不同层次的对象应该有不同的变化率或不同的变化来源,则该层次可以适应它们之间的剪切作用。
• CONCEPTUAL CONTOURS. If the objects of different layers should have different rates of change or different sources of change, the layer accommodates the shearing between them.
并非每个新模型都需要从头开始定义层。某些层会在相关的领域系列中出现。
It isn’t always necessary to start from scratch in defining layers for each new model. Certain layers show up in whole families of related domains.
例如,在以利用大型固定资本资产(如工厂或货船)为基础的企业中,物流软件通常可以组织成“潜力”层(在本例中是“能力”层的另一个名称)和“运营”层。
For example, in businesses based on exploiting large fixed capital assets, such as factories or cargo ships, logistical software can often be organized into a “Potential” layer (another name for the “Capability” layer in the example) and an “Operations” layer.
•潜力。我们可以做什么?暂且不论我们计划做什么,我们能做什么?组织的资源,包括人员,以及这些资源的组织方式,是潜力层的核心。与供应商签订的合同也定义了潜力。几乎在任何业务领域都能找到这一层,但在那些拥有相对较大的固定资本投资以支撑业务发展的行业(例如运输和制造业)中,潜力层尤为重要。潜力也包括临时资产,但主要由临时资产驱动的业务可能会选择强调这一点的层,这一点将在后文讨论。(在示例中,这一层被称为“能力”。)
• Potential. What can be done? Never mind what we are planning to do. What could we do? The resources of the organization, including its people, and the way those resources are organized are the core of the Potential layer. Contracts with vendors also define potentials. This layer could be recognized in almost any business domain, but it is a prominent part of the story in those businesses, such as transportation and manufacturing, that have relatively large fixed capital investments that enable the business. Potential includes transient assets as well, but a business driven primarily by transient assets might choose layers that emphasize this, as discussed later. (This layer was called “Capability” in the example.)
•运作。正在做什么?我们如何利用这些潜力?与潜力层一样,这一层应该反映实际情况,而不是我们希望它是什么样子。在这一层,我们试图审视自身的努力和活动:我们销售的是什么,而不是我们销售的手段是什么。操作层对象引用潜在层对象甚至由潜在层对象构成是很常见的,但潜在层对象不应该引用操作层。
• Operation. What is being done? What have we managed to make of those potentials? Like the Potential layer, this layer should reflect the reality of the situation, rather than what we want it to be. In this layer we are trying to see our own efforts and activities: What we are selling, rather than what enables us to sell. It is very typical of Operational objects to reference or even be composed of Potential objects, but a Potential object shouldn’t reference the Operations layer.
在许多(或许是大多数)此类领域的现有系统中,这两层涵盖了所有内容(尽管也可能存在完全不同且更具启发性的划分)。它们跟踪当前状况和正在实施的运营计划,并发布相关报告或文档。但跟踪并不总是足够的。当项目旨在指导或协助用户,或实现决策自动化时,还需要将一系列额外的职责组织到运营层之上的另一个层中。
In many, perhaps most, existing systems in domains of this kind, these two layers cover everything (although there could be some entirely different and more revealing breakdown). They track the current situation and active operational plans and issue reports or documents about it. But tracking is not always enough. When projects seek to guide or assist users, or to automate decision making, there is an additional set of responsibilities that can be organized into another layer, above Operations.
•决策支持。应该采取什么行动或制定什么政策?这一层用于分析和决策。它的分析基于来自较低层(例如潜力层或运营层)的信息。决策支持软件可能会利用历史信息,主动寻找当前和未来运营的机会。
• Decision Support. What action should be taken or what policy should be set? This layer is for analysis and decision making. It bases its analysis on information from lower layers, such as Potential or Operations. Decision Support software may use historical information to actively seek opportunities for current and future operations.
决策支持系统在概念上依赖于其他层,例如运营层或潜力层,因为决策并非孤立做出。许多项目都采用数据仓库技术来实现决策支持。该层构成了一个独立的“有界上下文”,与运营软件之间存在客户/供应商关系。在其他项目中,它则进行了更深层次的集成,如前面的扩展示例所示。分层结构的固有优势之一在于,较低层可以独立于较高层而存在。这有助于分阶段引入或在原有运营系统之上构建更高级别的增强功能。
Decision Support systems have conceptual dependencies on other layers such as Operations or Potential because decisions aren’t made in a vacuum. A lot of projects implement Decision Support using data warehouse technology. The layer becomes a distinct BOUNDED CONTEXT, with a CUSTOMER/SUPPLIER relationship with the Operations software. In other projects, it is more deeply integrated, as in the preceding extended example. And one of the intrinsic advantages of layers is that the lower layers can exist without the higher ones. This can facilitate phased introductions or higher-level enhancements built on top of older operational systems.
另一个例子是强制执行复杂业务规则或法律要求的软件,这可以构成责任层。
Another case is software that enforces elaborate business rules or legal requirements, which can constitute a RESPONSIBILITY LAYER.
•策略。规则和目标是什么?规则和目标大多是被动的,但会约束其他层级的行为。设计这些交互可能很微妙。有时,策略会作为参数传递给较低层级的方法。有时会应用策略模式。策略与以下因素结合使用效果很好:决策支持层,提供实现策略设定的目标的方法,并受策略设定的规则约束。
• Policy. What are the rules and goals? Rules and goals are mostly passive, but constrain the behavior in other layers. Designing these interactions can be subtle. Sometimes a Policy is passed in as an argument to a lower level method. Sometimes the STRATEGY pattern is applied. Policy works well in conjunction with a Decision Support layer, which provides the means to seek the goals set by Policy, constrained by the rules set by Policy.
策略层可以用与其他层相同的语言编写,但有时会使用规则引擎来实现。这并不一定意味着它们处于一个独立的有界上下文中。事实上,如果严格地在所有层中使用相同的模型,就可以减轻协调不同实现技术的难度。当规则基于与它们所应用的对象不同的模型编写时,要么复杂性会急剧增加,要么为了便于管理,对象会被简化。
Policy layers can be written in the same language as the other layers, but they are sometimes implemented using rules engines. This doesn’t necessarily place them in a separate BOUNDED CONTEXT. In fact, the difficulty of coordinating such different implementation technologies can be eased by fastidiously using the same model across both. When rules are written based on a different model than the objects they apply to, either the complexity goes way up or the objects get dumbed down to keep things manageable.
图 16.13. 工厂自动化系统中的概念依赖关系和剪切点
Figure 16.13. Conceptual dependencies and shearing points in a factory automation system
许多企业的能力并非取决于厂房和设备。例如,在金融服务或保险业,其潜力在很大程度上取决于当前的运营状况。保险公司承保新保单协议以承担新风险的能力,取决于其现有业务的多元化程度。“潜力”层很可能会并入“运营”层,并形成一种不同的层级结构。
Many businesses do not base their capability on plant and equipment. In financial services or insurance, to name two, the potential is to a large extent determined by current operations. An insurance company’s ability to take on a new risk by underwriting a new policy agreement is based on the diversification of its current business. The Potential layer would probably merge into Operations, and a different layering would evolve.
在这些情况下,经常凸显的一个领域是对客户做出的承诺。
One area that often comes to the fore in these situations is commitments made to customers.
•承诺。我们做出了哪些承诺?这一层既具有政策的性质,因为它阐明了指导未来运营的目标,又具有运营的性质,因为承诺会随着持续的业务活动而产生和变化。
• Commitment. What have we promised? This layer has the nature of Policy, in that it states goals that direct future operations, but it has the nature of Operations in that commitments emerge and change as a part of ongoing business activity.
图 16.14. 投资银行系统中的概念依赖关系和剪切点
Figure 16.14. Conceptual dependencies and shearing points in an investment banking system
“潜力”层和“承诺”层并非互斥。在某个领域,例如一家提供大量定制运输服务的运输公司,两者都非常重要,因此可以同时使用这两个层。针对这些领域,其他更具体的层也可能有用。要不断调整,不断尝试。但最好保持层级系统的简洁性;超过四层甚至五层就会变得难以驾驭。层级过多不利于有效阐述业务,而且原本旨在解决的复杂性问题会以新的形式再次出现。因此,必须对大型结构进行彻底的精简。
The Potential and Commitment layers are not mutually exclusive. A domain in which both are prominent, say a transportation company with a lot of custom shipping services, might use both. Other layers more specific to those domains might be useful too. Change things. Experiment. But it is best to keep the layering system simple; going beyond four or possibly five becomes unwieldy. Having too many layers isn’t as effective at telling the story, and the problems of complexity the large-scale structure was meant to solve will come back in a new form. The large-scale structure must be ferociously distilled.
尽管这五层架构适用于多种企业系统,但它们并不能涵盖所有领域的核心职责。在某些情况下,强行将设计套用这种模式可能会适得其反,但或许存在一套自然而然的职责层级组合,能够有效发挥作用。对于与我们讨论过的领域完全无关的领域,这些层级可能需要完全原创。最终,你需要运用直觉,从某个起点开始,让秩序自然演进。
Although these five layers are applicable to a range of enterprise systems, they do not capture the salient responsibilities of all domains. In other cases, it would be counterproductive to try to force the design into this shape, but there may be a natural set of RESPONSIBILITY LAYERS that do work. For a domain completely unrelated to those we’ve discussed, these layers might have to be completely original. Ultimately, you have to use your intuition, start somewhere, and let the ORDER EVOLVE.
[知识水平]是指一组对象,它描述了另一组对象应该如何表现。[Martin Fowler,“问责制”,www.martinfowler.com ]
[A KNOWLEDGE LEVEL is] a group of objects that describes how another group of objects should behave. [Martin Fowler, “Accountability,” www.martinfowler.com]
知识水平(Knowledge Level)在我们需要让模型的某些部分在用户手中具有一定的灵活性,同时又受到更广泛的规则约束时,能够帮助我们理清思路。它满足了可配置行为软件的需求,在这些软件中,实体(ENTITIES)之间的角色和关系必须在安装时甚至运行时进行更改。
KNOWLEDGE LEVEL untangles things when we need to let some part of the model itself be plastic in the user’s hands yet constrained by a broader set of rules. It addresses requirements for software with configurable behavior, in which the roles and relationships among ENTITIES must be changed at installation or even at runtime.
在《分析模式》(Fowler 1996,第24-27页)一书中,知识层模式源于对组织内部问责制建模的讨论,随后被应用于会计中的记账规则。尽管该模式在多个章节中出现,但由于它与书中大多数模式不同,因此没有单独成章。与其他分析模式对领域进行建模不同,知识层模式构建的是一个模型。
In Analysis Patterns (Fowler 1996, pp. 24–27), the pattern emerges from a discussion of modeling accountability within organizations, and it is later applied to posting rules in accounting. Although the pattern appears in several chapters, it doesn’t have a chapter of its own because it is different from most patterns in the book. Rather than modeling a domain, as the other analysis patterns do, KNOWLEDGE LEVEL structures a model.
为了更具体地理解这个问题,我们可以考虑“问责制”模型。组织由人员和更小的组织构成,并明确定义他们扮演的角色以及彼此之间的关系。不同组织中,这些角色和关系的规则差异很大。在一家公司,一个“部门”可能由一位“总监”领导,他向一位“副总裁”汇报。而在另一家公司,一个“模块”可能由一位“经理”领导,他向一位“高级经理”汇报。此外,还有“矩阵式”组织,在这种组织中,每个人出于不同的目的向不同的经理汇报。
To see the problem concretely, consider models of “accountability.” Organizations are made up of people and smaller organizations, and define the roles they play and the relationships between them. The rules governing those roles and relationships vary greatly for different organizations. At one company, a “department” might be headed by a “Director” who reports to a “Vice President.” In another company, a “module” is headed by a “Manager” who reports to a “Senior Manager.” Then there are “matrix” organizations, in which each person reports to different managers for different purposes.
典型的应用程序会做出一些假设。当这些假设不成立时,用户就会开始以不同于预期的方式使用数据输入字段。由于语义已被用户改变,应用程序的任何行为都会失效。用户要么会开发出应对这种行为的变通方法,要么会寻求更高的权限。应用程序的高级功能将被禁用。他们将被迫学习复杂的映射关系,将他们的工作内容与软件的运行方式联系起来。这样一来,他们将永远无法获得良好的服务。
A typical application would make some assumptions. When those didn’t fit, users would start to use data-entry fields in a different way than they were intended. Any behavior the application had would misfire, as the semantics were changed by the users. Users would develop workarounds for the behavior, or would get the higher level features of the application shut off. They would be forced to learn complicated mappings between what they did in their jobs and the way the software works. They would never be served well.
当系统需要更改或替换时,开发人员迟早会发现,某些功能的实际含义并非表面看起来那样简单。它们在不同的用户群体或不同场景下可能意味着截然不同的事情。在不破坏这些叠加使用方式的前提下进行任何更改都将是一项艰巨的任务。将数据迁移到更定制化的系统需要理解并针对所有这些特殊情况进行编码。
When the system had to be changed or replaced, developers would discover (sooner or later) that the meanings of the features were not what they seemed. They might mean very different things in different user communities or in different situations. Changing anything without breaking these overlaid usages would be daunting. Data migration to a more tailored system would require understanding and coding for all those quirks.
一家中型公司的人力资源部门有一个简单的程序来计算工资和养老金缴款。
The HR department of a medium-sized company has a simple program for calculating payroll and pension contributions.
图 16.15. 旧模型,对新要求过于约束
Figure 16.15. The old model, overconstrained for new requirements
图 16.16. 使用旧模型表示的部分员工
Figure 16.16. Some employees represented using the old model
但现在,管理层决定让办公室行政人员加入“固定收益”退休计划。问题在于,办公室行政人员是按小时计薪的,而这种模式不允许混合使用不同的薪酬方式。因此,必须改变这种模式。
But now, the management has decided that the office administrators should go into the “defined benefit” retirement plan. The trouble is that office administrators are paid hourly, and this model does not allow mixing. The model will have to change.
下一个模型方案非常简单:只需移除约束条件即可。
The next model proposal is quite simple: just remove the constraints.
图 16.17. 所提出的模型,目前处于欠约束状态
Figure 16.17. The proposed model, now underconstrained
图 16.18. 员工可能被分配到错误的计划中。
Figure 16.18. Employees can be associated with the wrong plan.
这种模式允许每位员工同时参与两种退休计划,因此每位办公室管理员都可以轮换。管理层拒绝了这种模式,因为它不符合公司政策。有些管理员可以轮换,有些则不行。甚至清洁工也可以轮换。管理层想要的是一种能够强制执行公司政策的模式:
This model allows each employee to be associated with either kind of retirement plan, so each office administrator can be switched. This model is rejected by management because it does not reflect company policy. Some administrators could be switched and others not. Or the janitor could be switched. Management wants a model that enforces the policy:
办公室行政人员是按小时计薪的员工,享有固定收益退休计划。
Office administrators are hourly employees with defined-benefit retirement plans.
这项政策表明,“职位名称”字段现在代表了一个重要的领域概念。开发人员可以重构代码,将该概念明确地定义为“员工类型”。
This policy suggests that the “job title” field now represents an important domain concept. Developers could refactor to make that concept explicit as an “Employee Type.”
图 16.19. Type对象允许满足要求。
Figure 16.19. The Type object allows requirements to be met.
图 16.20。每种员工类型都分配有一个退休计划。
Figure 16.20. Each Employee Type is assigned a Retirement Plan.
这些要求可以用通用语言表述如下:
The requirements can be stated in the UBIQUITOUS LANGUAGE as follows:
员工类型可以分配给退休计划或工资计划。
An Employee Type is assigned to either Retirement Plan or either payroll.
员工受限于员工类型。
Employees are constrained by the Employee Type.
只有“超级用户”才有权限编辑“员工类型”对象。仅当公司政策发生变化时才会进行更改。人事部门的普通用户可以更改员工信息或将其指向不同的员工类型。
Access to edit the Employee Type object will be restricted to a “superuser,” who will make changes only when company policy changes. An ordinary user in the personnel department can change Employees or point them at a different Employee Type.
这个模型满足了要求。开发人员隐约感觉到一两个概念,但目前只是一种挥之不去的预感。他们还没有任何具体的想法可以深入研究,所以决定今天就到此为止。
This model satisfies the requirements. The developers sense an implicit concept or two, but it is just a nagging feeling at the moment. They don’t have any solid ideas to pursue, so they call it a day.
静态模型可能会引发问题。但完全灵活的系统允许呈现任何可能的关系,同样会带来严重的问题。这样的系统使用起来很不方便,而且无法确保组织自身规则的执行。
A static model can cause problems. But problems can be just as bad with a fully flexible system that allows any possible relationship to be presented. Such a system would be inconvenient to use and wouldn’t allow the organization’s own rules to be enforced.
为每个组织完全定制软件是不切实际的,因为即使每个组织都能支付定制软件的费用,组织结构也可能会经常发生变化。
Fully customizing software for each organization is not practical because, even if each organization could pay for custom software, the organizational structure will likely change frequently.
因此,这类软件必须提供选项,允许用户根据组织当前的结构进行配置。问题在于,向模型对象添加这些选项会使它们变得难以管理。灵活性越高,整体就越复杂。
So such software must provide options to allow the user to configure it to reflect the current structure of the organization. The trouble is that adding such options to the model objects makes them unwieldy. The more flexibility you add, the more complex it all becomes.
在实体间角色和关系随情况变化的应用中,复杂性可能会呈爆炸式增长。无论是完全通用的模型还是高度定制化的模型都无法满足用户的需求。为了涵盖各种情况,对象最终会引用其他类型,或者拥有在不同情况下以不同方式使用的属性。即使拥有相同的数据和行为,为了适应不同的程序集规则,类的数量也可能成倍增加。
In an application in which the roles and relationships between ENTITIES vary in different situations, complexity can explode. Neither fully general models nor highly customized ones serve the users’ needs. Objects end up with references to other types to cover a variety of cases, or with attributes that are used in different ways in different situations. Classes that have the same data and behavior may multiply just to accommodate different assembly rules.
我们的模型中嵌套着另一个关于我们自身模型的模型。知识层级将模型的自我定义部分分离出来,并明确地阐明了其约束条件。
Nestled into our model is another model that is about our model. A KNOWLEDGE LEVEL separates that self-defining aspect of the model and makes its constraints explicit.
知识层是反射模式领域层的一种应用,广泛应用于许多软件架构和技术基础设施中,Buschmann 等人在 1996 年的论文中有详细描述。反射模式通过使软件具备“自我感知”能力来适应不断变化的需求,并使软件结构和行为的特定方面易于调整和修改。这是通过将软件拆分为一个“基础层”来实现的,该基础层负责运维工作。对于应用程序而言,以及一个“元级别”,它代表了对软件结构和行为的了解。
KNOWLEDGE LEVEL is an application to the domain layer of the REFLECTION pattern, used in many software architectures and technical infrastructures and described well in Buschmann et al. 1996. REFLECTION accommodates changing needs by making the software “self-aware,” and making selected aspects of its structure and behavior accessible for adaptation and change. This is done by splitting the software into a “base level,” which carries the operational responsibility for the application, and a “meta level,” which represents knowledge of the structure and behavior of the software.
值得注意的是,这种模式并不被称为知识“层”。尽管它与分层结构非常相似,但反射模式涉及双向的相互依赖关系。
Significantly, the pattern is not called a knowledge “layer.” As much as it resembles layering, REFLECTION involves mutual dependencies running in both directions.
Java 内置了一些基本的反射机制,以协议的形式用于查询类的方法等信息。这种机制允许程序询问自身的设计。CORBA 拥有更丰富但类似的反射协议。一些持久化技术扩展了这种自描述的能力,支持数据库表和对象之间的部分自动化映射。还有其他一些技术示例。这种模式也可以应用于领域层。
Java has some minimal built-in REFLECTION in the form of protocols for interrogating a class for its methods and so forth. Such mechanisms allow a program to ask questions about its own design. CORBA has somewhat more extensive but similar REFLECTION protocols. Some persistence technologies extend the richness of that self-description to support partially automated mapping between database tables and objects. There are other technical examples. This pattern can also be applied within the domain layer.
比较知识水平和反思的术语
Comparing the terminology of KNOWLEDGE LEVEL and REFLECTION
需要明确的是,编程语言的反射工具并非用于实现领域模型的知识层级。这些元对象描述的是语言构造本身的结构和行为。相反,知识层级必须由普通对象构建而成。
Just to be clear, the reflection tools of the programming language are not for use in implementing the KNOWLEDGE LEVEL of a domain model. Those meta-objects describe the structure and behavior of the language constructs themselves. Instead, the KNOWLEDGE LEVEL must be built of ordinary objects.
知识层提供了两个有用的区别。首先,它侧重于应用领域,这与常见的反思用法不同。其次,它并不追求完全的通用性。正如规范比一般谓词更有用一样,针对一组对象及其关系的非常具体的约束集可能比通用框架更有用。知识层更简洁,能够传达设计者的具体意图。
The KNOWLEDGE LEVEL provides two useful distinctions. First, it focuses on the application domain, in contrast to familiar uses of REFLECTION. Second, it does not strive for full generality. Just as a SPECIFICATION can be more useful than a general predicate, a very specialized set of constraints on a set of objects and their relationships can be more useful than a generalized framework. The KNOWLEDGE LEVEL is simpler and can communicate the specific intent of the designer.
创建一组独立的对象,用于描述和约束基本模型的结构和行为。将这些关注点分为两个“层级”:一个非常具体,另一个反映用户或超级用户可以自定义的规则和知识。
Create a distinct set of objects that can be used to describe and constrain the structure and behavior of the basic model. Keep these concerns separate as two “levels,” one very concrete, the other reflecting rules and knowledge that a user or superuser is able to customize.
如同所有强大的理念一样,“反思”和“知识层级”模式也可能令人着迷。这种模式应谨慎使用。它可以通过让操作对象摆脱“万事通”的束缚来简化复杂性,但它引入的间接性也会带来一些晦涩之处。如果“知识层级”变得过于复杂,那么开发者和用户都将难以理解系统的行为。配置系统的用户(或超级用户)最终需要具备程序员的技能——而且是元程序员级别的技能。如果他们犯错,应用程序就会运行异常。
Like all powerful ideas, REFLECTION and KNOWLEDGE LEVELS can be intoxicating. This pattern should be used sparingly. It can unravel complexity by freeing operations objects from the need to be jacks-of-all-trades, but the indirection it introduces does add some of that obscurity back in. If the KNOWLEDGE LEVEL becomes complex, the system’s behavior becomes hard to understand for developers and users alike. The users (or superuser) who configure it will end up needing the skills of a programmer—and a meta-level programmer at that. If they make mistakes, the application will behave incorrectly.
此外,数据迁移的基本问题并不会完全消失。当知识层结构发生变化时,现有的操作层对象也必须进行相应的处理。新旧对象或许可以共存,但无论如何,都需要进行仔细的分析。
Also, the basic problems of data migration don’t completely disappear. When a structure in the KNOWLEDGE LEVEL is changed, existing operations-level objects have to be dealt with. It may be possible for old and new to coexist, but one way or another, careful analysis is needed.
所有这些问题都给知识层级的设计者带来了沉重的负担。设计必须足够稳健,不仅要能应对开发过程中出现的各种场景,还要能应对用户未来可能对软件进行的任何配置。如果运用得当,知识层级可以解决那些难以用其他方式解决的问题,尤其是在定制化至关重要且否则会扭曲设计的地方。
All of these issues put a major burden on the designer of a KNOWLEDGE LEVEL. The design has to be robust enough to handle not only the scenarios presented in development, but also any scenario for which a user could configure the software in the future. Applied judiciously, to the points where customization is crucial and would otherwise distort the design, KNOWLEDGE LEVELS can solve problems that are very hard to handle any other way.
我们的团队成员都回来了,经过一夜的休息,精神焕发,其中一位开始着手解决一个棘手的问题。为什么?某些对象是否受到保护,而另一些对象则可以自由编辑?这组受限对象让他想起了知识层级模式,于是他决定尝试用这种模式来审视模型。他发现现有的模型其实已经可以这样理解了。
Our team members are back, and, refreshed from a night’s sleep, one of them has started to close in on one of the awkward points. Why were certain objects being secured while others were freely edited? The cluster of restricted objects reminded him of the KNOWLEDGE LEVEL pattern, and he decided to try it as a way of viewing the model. He found that the existing model could already be viewed this way.
图 16.21. 识别现有模型中隐含的知识水平
Figure 16.21. Recognizing the KNOWLEDGE LEVEL implicit in the existing model
受限的编辑权限位于知识层,而日常编辑权限位于操作层。这种安排非常合理。以上所有对象都描述了类型或长期策略。“员工类型”有效地规定了员工的行为。
The restricted edits were in the KNOWLEDGE LEVEL, while the day-to-day edits were in the operational level. A nice fit. All the objects above the line described types or longstanding policies. The Employee Type effectively imposed behavior on the Employee.
这位开发人员正在与同事分享他的见解,这时另一位开发人员又有了新的发现。通过按知识水平组织模型,她清晰地看到了困扰她一天的问题所在:两个截然不同的概念被混杂在同一个对象中。她昨天听到的讨论中也提到了这一点,但当时并没有准确理解。
The developer was sharing his insight with his colleagues when one of the other developers had another insight. The clarity of seeing the model organized by KNOWLEDGE LEVEL had let her spot what had been bothering her the previous day. Two distinct concepts were being combined in the same object. She had heard it in the language used on the previous day but hadn’t put her finger on it:
员工类型可以分配给退休计划或工资计划。
An Employee Type is assigned to either Retirement Plan or either payroll.
但这并非一句用通用语言表达的陈述。模型中并没有“工资”这个概念。他们用的是他们想要的语言,而不是他们实际拥有的语言。工资的概念隐含在模型中,与员工类型混为一谈。在知识水平被单独列出之前,这一点并不那么明显,而这个关键短语中的所有要素都出现在同一层级……只有一个例外。
But that was not really a statement in the UBIQUITOUS LANGUAGE. There was no “payroll” in the model. They had spoken in the language they wanted, rather than the one they had. The concept of payroll was implicit in the model, lumped together with Employee Type. It hadn’t been so obvious before the KNOWLEDGE LEVEL was separated out, and the very elements in that key phrase all appeared in the same level together . . . except one.
Based on this insight, she refactored again to a model that does support that statement.
用户对关联对象规则的控制需求促使团队采用了一种隐含知识水平的模型。
The need for user control of the rules for associating objects drove the team to a model that had an implicit KNOWLEDGE LEVEL.
图 16.22.工资现在是明确的,与员工类型不同。
Figure 16.22. Payroll is now explicit, distinct from Employee Type.
图 16.23。每种员工类型现在都有退休计划和工资单。
Figure 16.23. Each Employee Type now has a Retirement Plan and a Payroll.
知识水平的概念可以从其特有的访问限制和“事物-事物”类型的关系中得到暗示。一旦确定了知识水平,它所带来的清晰度就有助于产生另一个洞见,即通过剔除“薪资”这一因素,将两个重要的领域概念区分开来。
KNOWLEDGE LEVEL was hinted at by the characteristic access restrictions and a “thing-thing” type relationship. Once it was in place, the clarity it afforded helped produce another insight that disentangled two important domain concepts by factoring out Payroll.
知识水平(Knowledge Level)与其他大型结构一样,并非绝对必要。即使没有它,对象仍然可以正常工作,而且之前区分员工类型和薪资的洞察仍然可以被发现和利用。或许将来某个时候,这个结构会显得力不从心,可以被移除。但就目前而言,它似乎能够清晰地展现系统的运作方式,并帮助开发人员更好地理解模型。
KNOWLEDGE LEVEL, like other large-scale structures, isn’t strictly necessary. The objects will still work without it, and the insight that separated Employee Type from Payroll could still have been found and used. There may come a time when this structure doesn’t seem to be pulling its weight and can be dropped. But for now, it seems to tell a useful story about the system and helps developers grapple with the model.
乍一看,知识层似乎是责任层的一个特例,尤其是“策略”层,但实际上并非如此。首先,知识层各层之间的依赖关系是双向的,而责任层中,下层与上层是相互独立的。
At first glance, KNOWLEDGE LEVEL looks like a special case of RESPONSIBILITY LAYERS, especially the “policy” layer, but it is not. For one thing, dependencies run in both directions between the levels, but with LAYERS, lower layers are independent of upper layers.
事实上,知识水平可以与大多数其他大型结构共存,为组织提供额外的维度。
In fact, KNOWLEDGE LEVEL can coexist with most other large-scale structures, providing an additional dimension of organization.
机遇往往出现在成熟、深入且精炼的模型中。可插拔组件框架通常只有在同一领域内已经实现了一些应用程序之后才会发挥作用。
Opportunities arise in a very mature model that is deep and distilled. A PLUGGABLE COMPONENT FRAMEWORK usually only comes into play after a few applications have already been implemented in the same domain.
当多个基于相同抽象概念但独立设计的应用程序需要互操作时,多个限界上下文之间的转换会限制集成。对于不紧密协作的团队来说,共享内核是不可行的。重复和碎片化会增加开发和安装成本,并使互操作性变得非常困难。
When a variety of applications have to interoperate, all based on the same abstractions but designed independently, translations between multiple BOUNDED CONTEXTS limit integration. A SHARED KERNEL is not feasible for teams that do not work closely together. Duplication and fragmentation raise costs of development and installation, and interoperability becomes very difficult.
一些成功的项目会将设计分解成多个组件,每个组件负责特定类别的功能。通常,所有组件都会接入一个中央枢纽,该枢纽支持它们所需的任何协议,并知道如何与它们提供的接口通信。当然,组件的连接方式还有其他选择。这些接口的设计以及连接它们的枢纽的设计必须协调一致,而内部组件的设计则可以更加独立。
Some successful projects break down their design into components, each with responsibility for certain categories of functions. Usually all the components plug into a central hub, which supports any protocols they need and knows how to talk to the interfaces they provide. Other patterns of connecting components are also possible. The design of these interfaces and the hub that connects them must be coordinated, while more independence is possible designing the interiors.
一些广泛使用的技术框架支持这种模式,但这只是次要问题。只有当技术框架能够解决某些关键的技术问题(例如分布式开发或在不同应用程序之间共享组件)时,才需要它。这种模式的基本原理是对职责进行概念性的组织,可以轻松地应用于单个 Java 程序中。
Several widely used technical frameworks support this pattern, but that is a secondary issue. A technical framework is needed only if it solves some essential technical problem such as distribution, or sharing a component among different applications. The basic pattern is a conceptual organization of responsibilities. It can easily be applied within a single Java program.
所以:
Therefore:
提炼出接口和交互的抽象核心,并创建一个框架,允许自由替换这些接口的各种实现。同样地,允许任何应用程序使用这些组件,只要它严格按照抽象核心的接口运行即可。
Distill an ABSTRACT CORE of interfaces and interactions and create a framework that allows diverse implementations of those interfaces to be freely substituted. Likewise, allow any application to use those components, so long as it operates strictly through the interfaces of the ABSTRACT CORE.
系统内部识别并共享高级抽象概念;专门化则发生在模块中。应用程序的核心是共享内核中的抽象核心。但封装组件背后可能存在多个有界上下文。这种结构具有接口,因此当许多组件来自许多不同的来源,或者当组件封装预先存在的软件以进行集成时,这种结构会特别方便。
High-level abstractions are identified and shared across the breadth of the system; specialization occurs in MODULES. The central hub of the application is an ABSTRACT CORE within a SHARED KERNEL. But multiple BOUNDED CONTEXTS can lie behind the encapsulated component interfaces, so that this structure can be especially convenient when many components are coming from many different sources, or when components are encapsulating preexisting software for integration.
这并非意味着组件必须采用不同的模型。如果团队持续集成,则可以在同一个上下文中开发多个组件;或者,他们可以定义另一个共享内核,供一组密切相关的组件共同使用。所有这些策略都可以在大规模可插拔组件结构中轻松共存。在某些情况下,还可以选择使用已发布的语言作为中心的插件接口。
This is not to say that components must have divergent models. Multiple components can be developed within a single CONTEXT if the teams CONTINUOUSLY INTEGRATE, or they can define another SHARED KERNEL held in common by a closely related set of components. All these strategies can coexist easily within a large-scale structure of PLUGGABLE COMPONENTS. Another option, in some cases, is to use a PUBLISHED LANGUAGE for the plug-in interface of the hub.
可插拔组件框架存在一些缺点。首先,这种模式应用起来非常困难。它要求接口设计必须非常精确,并且需要一个足够深的模型来捕捉抽象核心中所需的行为。另一个主要缺点是应用程序的选择有限。如果应用程序需要对核心领域采用截然不同的方法,这种结构就会成为阻碍。开发人员可以对模型进行特化,但如果不更改所有不同组件的协议,就无法更改抽象核心。因此,持续改进核心、重构以获得更深入理解的过程几乎停滞不前。
There are a few downsides to a PLUGGABLE COMPONENT FRAMEWORK. One is that this is a very difficult pattern to apply. It requires precision in the design of the interfaces and a deep enough model to capture the necessary behavior in the ABSTRACT CORE. Another major downside is that applications have limited options. If an application needs a very different approach to the CORE DOMAIN, the structure will get in the way. Developers can specialize the model, but they can’t change the ABSTRACT CORE without changing the protocol of all the diverse components. As a result, the process of continuous refinement of the CORE, refactoring toward deeper insight, is more or less frozen in its tracks.
Fayad 和 Johnson (2000)对多个领域中可插拔组件框架的雄心勃勃的尝试进行了深入探讨,其中包括对 SEMATECH CIM 的讨论。此类框架的成功与否参差不齐。或许最大的障碍在于设计一个实用框架所需的理解成熟度。可插拔组件框架不应是项目中应用的第一个或第二个大型结构。最成功的案例都是在多个专业应用程序完全开发完成后才出现的。
Fayad and Johnson (2000) give a good look at ambitious attempts at PLUGGABLE COMPONENT FRAMEWORKS in several domains, including a discussion of SEMATECH CIM. The success of such frameworks is a mixed story. Probably the biggest obstacle is the maturity of understanding needed to design a useful framework. A PLUGGABLE COMPONENT FRAMEWORK should not be the first large-scale structure applied on a project, nor the second. The most successful examples have followed after the full development of multiple specialized applications.
在一家生产计算机芯片的工厂里,成组(称为批次)的硅晶圆经过数百道工序,从一台机器转移到另一台机器,直到在其上完成微观电路的印刷和蚀刻。这家工厂需要一款能够实现这一目标的软件。跟踪每个批次,记录其具体的加工过程,然后指示工厂工人或自动化设备将其送至下一台合适的机器,并执行下一步的加工工序。这种软件称为制造执行系统(MES)。
In a factory producing computer chips, groups (called lots) of silicon wafers are moved from one machine to another through hundreds of steps of processing until the microscopic circuitry being printed and etched into them is complete. The factory needs software that can track each individual lot, recording the exact processing that has been done to it, and then direct either factory workers or automated equipment to take it to the next appropriate machine and apply the next appropriate process. Such software is called a manufacturing execution system (MES).
该工艺涉及使用来自数十家供应商的数百台不同机器,并且每个步骤都采用精心定制的配方。开发能够处理如此复杂组合的制造执行系统 (MES) 软件难度极大,成本也极其高昂。为此,行业联盟 SEMATECH 开发了 CIM 框架。
Hundreds of different machines from dozens of vendors are used, with carefully tailored recipes at each step of the way. Developing MES software that could deal with such a complex mix was daunting and prohibitively expensive. In response, an industry consortium, SEMATECH, developed the CIM Framework.
CIM框架庞大而复杂,涉及诸多方面,但其中两点与本文相关。首先,该框架为半导体MES领域的基本概念定义了抽象接口——换句话说,就是以抽象核心的形式定义了核心领域。这些接口定义涵盖了行为和语义两方面。
The CIM Framework is big and complicated and has many aspects, but two are relevant here. First, the framework defines abstract interfaces for the basic concepts of the semiconductor MES domain—in other words, the CORE DOMAIN in the form of an ABSTRACT CORE. These interface definitions include both behavior and semantics.
图 16.24. CIM 接口的高度简化子集,以及示例实现
Figure 16.24. A highly simplified subset of the CIM interfaces, with sample implementations
如果供应商生产新机器,则必须开发流程机器接口的专用实现方案。如果他们遵循该接口,则其机器控制组件应能与任何基于 CIM 框架的应用程序兼容。
If a vendor produces a new machine, they have to develop a specialized implementation of the Process Machine interface. If they adhere to that interface, their machine-control component should plug into any application based on the CIM Framework.
在定义了这些接口之后,SEMATECH 还定义了它们在应用程序中交互的规则。任何基于 CIM 框架的应用程序都必须实现一个协议,该协议托管实现了这些接口子集的对象。如果实现了该协议,并且应用程序严格遵守抽象接口,那么应用程序就可以获得承诺的功能。无论具体实现方式如何,这些接口提供的服务都适用。这些接口及其使用协议的组合构成了一个限制性极强的大规模结构。
Having defined these interfaces, SEMATECH defined the rules by which they could interact in an application. Any application based on the CIM Framework would have to implement a protocol that hosted objects implementing some subset of those interfaces. If this protocol were implemented, and the application strictly observed the abstract interfaces, then the application could count on the promised services of those interfaces, regardless of implementation. The combination of those interfaces and the protocol for using them constitutes a tightly restrictive large-scale structure.
图 16.25。用户将货物放入下一台机器中,并将移动记录到计算机中。
Figure 16.25. The user places a lot in the next machine and logs the move into the computer.
该框架对基础设施有着非常具体的要求。它与 CORBA 紧密耦合,以提供持久化、事务、事件和其他技术服务。但它最引人注目之处在于其可插拔组件框架的定义,这使得人们能够独立开发软件,并将其无缝集成到庞大的系统中。虽然没有人了解此类系统的全部细节,但每个人都能理解其大致轮廓。
The framework has very specific infrastructure requirements. It is tightly coupled to CORBA to provide persistence, transactions, events, and other technical services. But the interesting thing about it is the definition of a PLUGGABLE COMPONENT FRAMEWORK, which allows people to develop software independently and smoothly integrate them into immense systems. No one knows all the details of such a system, but everyone understands an overview.
数千人如何独立工作,制作出由超过 40,000 块布料组成的拼布被?
How can thousands of people work independently to create a quilt of more than 40,000 panels?
几条简单的规则为艾滋病纪念被提供了一个大致的框架,细节则留给了各个贡献者自行处理。请注意这些规则是如何围绕整体目标(纪念逝者)展开的。死于艾滋病的人),使集成切实可行的组件特性,以及以较大部分处理被子的能力(例如折叠被子)。
A few simple rules provide a large-scale structure for the AIDS Memorial Quilt, leaving the details to individual contributors. Notice how the rules focus on the overall mission (memorializing people who have died of AIDS), the features of a component that make integration practical, and the ability to handle the quilt in larger sections (such as folding it).
Here’s How to Create a Panel for the Quilt
[摘自艾滋病纪念被项目网站www.aidsquilt.org ]
[From the AIDS Memorial Quilt Project Web site, www.aidsquilt.org]
设计面板
Design the panel
请写上您要缅怀的人的姓名。您也可以添加其他信息,例如出生日期、逝世日期和家乡。……请每个版块仅限一人。……
Include the name of the person you are remembering. Feel free to include additional information such as the dates of birth and death, and a hometown. . . . [P]lease limit each panel to one individual . . . .
选择您的材料
Choose your materials
请记住,被子需要多次折叠和展开,因此耐用性至关重要。由于胶水会随着时间推移而失效,最好将物品缝制在布料上。中等重量、无弹性的面料,例如棉帆布或府绸,是最佳选择。
Remember that the Quilt is folded and unfolded many times, so durability is crucial. Since glue deteriorates with time, it is best to sew things to the panel. A medium-weight, non-stretch fabric such as a cotton duck or poplin works best.
您的设计可以是竖向或横向,但成品布料(缝边后)尺寸必须为 3 英尺 x 6 英尺(90 厘米 x 180 厘米)——不多不少!裁剪布料时,请在每边预留 2-3 英寸的缝边。如果您无法自行缝边,我们可以为您代劳。布料无需填充棉絮,但建议使用衬布。衬布有助于保持布料平铺在地面上的清洁,并有助于保持布料的形状。
Your design can be vertical or horizontal, but the finished, hemmed panel must be 3 feet by 6 feet (90 cm × 180 cm)—no more and no less! When you cut the fabric, leave an extra 2–3 inches on each side for a hem. If you can’t hem it yourself, we’ll do it for you. Batting for the panels is not necessary, but backing is recommended. Backing helps to keep panels clean when they are laid out on the ground. It also helps retain the shape of the fabric.
创建面板
Create the panel
在制作面板时,您可能需要使用以下一些技巧:
In constructing your panel you might want to use some of the following techniques:
• 贴布绣:将布料、字母和小纪念品缝到底布上。不要用胶水——胶水不牢固。
• Appliqué: Sew fabric, letters and small mementos onto the background fabric. Do not rely on glue—it won’t last.
• 上色:用刷子涂抹纺织颜料或不褪色染料,或者使用不褪色墨水笔。请不要使用“立体”颜料,它太粘稠了。
• Paint: Brush on textile paint or color-fast dye, or use an indelible ink pen. Please don’t use “puffy” paint; it’s too sticky.
• 模板:用铅笔在布料上描绘出图案,提起模板,然后用刷子涂抹纺织颜料或不褪色记号笔。
• Stencil: Trace your design onto the fabric with a pencil, lift the stencil, then use a brush to apply textile paint or indelible markers.
• 拼贴画:确保添加到面板上的任何材料都不会撕裂织物(因此请避免使用玻璃和亮片),并且一定要避免使用非常笨重的物体。
• Collage: Make sure that whatever materials you add to the panel won’t tear the fabric (avoid glass and sequins for this reason), and be sure to avoid very bulky objects.
• 照片:添加照片或文字的最佳方法是将其复印到热转印纸上,熨烫到 100% 纯棉布料上,然后将布料缝到面板上。您也可以将照片放入透明塑料薄膜中,然后将其缝到面板上(偏离中心缝,以免缝到折叠处)。
• Photos: The best way to include photos or letters is to photocopy them onto iron-on transfers, iron them onto 100% cotton fabric and sew that fabric to the panel. You may also put the photo in clear plastic vinyl and sew it to the panel (off-center so it avoids the fold).
本章讨论的大规模结构模式涵盖了从非常宽松的“系统隐喻”到限制严格的“可插拔组件框架”等多种类型。当然,其他结构也是可能的,即使在通用的结构模式内,规则的限制程度也有很多选择。
The large-scale structure patterns discussed in this chapter range from the very loose SYSTEM METAPHOR to the restrictive PLUGGABLE COMPONENT FRAMEWORK. Other structures are possible, of course, and even within a general structural pattern, there is a lot of choice about how restrictive to make the rules.
例如,责任层规定了模型概念及其依赖关系的一种分解方式,但您可以添加规则来指定层之间的通信模式。
For example, RESPONSIBILITY LAYERS dictate a kind of factoring of model concepts and their dependencies, but you could add rules that would specify communication patterns between the layers.
设想一个制造工厂,软件会根据预设的流程将每个零件送往相应的机器进行加工。正确的加工流程由策略层发出指令,并在操作层执行。然而,生产现场难免会出现错误,实际情况与软件规则不符。操作层必须反映真实世界,这意味着当某个零件偶尔被送入错误的机器时,必须无条件地接受这一信息。这种异常情况需要以某种方式传递给更高层。决策层随后可以使用其他策略来纠正这种情况,例如将零件重新送至维修流程或直接报废。但操作层对更高层的情况一无所知。因此,这种信息传递必须避免在底层和上层之间建立双向依赖关系。
Consider a manufacturing plant where software directs each part to a machine where it is processed according to some recipe. The correct process is ordered from a Policy layer and executed in an Operations layer. But inevitably there will be mistakes made on the factory floor. The actual situation will not be consistent with the rules of the software. Now, an Operations layer must reflect the world as it is, which means that when a part is occasionally put in the wrong machine, that information must be accepted unconditionally. Somehow, this exceptional condition needs to be communicated to a higher layer. A decision-making layer can then use other policies to correct the situation, perhaps by rerouting the part to a repair process or by scrapping it. But Operations does not know anything about higher layers. The communication has to be done in a way that doesn’t create two-way dependencies from the lower layers to the higher ones.
通常,这种信号传递是通过某种事件机制实现的。操作对象会在其状态发生变化时生成事件。策略层对象会监听来自底层的相关事件。当发生违反规则的事件时,该规则会执行相应的操作(规则定义的一部分)以做出相应的响应,或者生成一个事件以供更高层使用。
Typically, this signaling would be done through some kind of event mechanism. The Operations objects would generate events whenever their state changed. Policy layer objects would listen for events of interest from the lower layers. When an event occurred that violated a rule, the rule would execute an action (part of the rule’s definition) that makes the appropriate response, or it might generate an event for the benefit of some still higher layer.
以银行业为例,资产价值会发生变化(运营),从而影响投资组合中各部分的价值。当这些价值超过投资组合的配置限额(政策)时,系统可能会发出警报,提醒交易员买卖资产以调整资产平衡。
In the banking example, the values of assets change (Operations), shifting the values of segments of a portfolio. When these values exceed portfolio allocation limits (Policy), perhaps a trader is alerted, who can buy or sell assets to redress the balance.
我们可以逐个案例地解决这个问题,也可以为所有人制定一个统一的模式,用于处理特定层级对象之间的交互。更严格的结构可以提高统一性,使设计更容易理解。如果结构合适,规则更有可能促使开发者设计出优秀的产品。不同的组件也更有可能更好地契合在一起。
We could figure this out on a case-by-case basis, or we could decide on a consistent pattern for everyone to follow in interactions of objects of particular layers. A more restrictive structure increases uniformity, making the design easier to interpret. If the structure fits, the rules are likely to push developers toward good designs. Disparate pieces are likely to fit together better.
另一方面,这些限制可能会剥夺开发者所需的灵活性。在异构系统中,尤其是在不同的实现技术中,某些特定的通信路径可能难以在有界上下文中应用。
On the other hand, the restrictions may take away flexibility that developers need. Very particular communication paths might be impractical to apply across BOUNDED CONTEXTS, especially in different implementation technologies, in a heterogeneous system.
因此,你必须克制住构建框架和规范大型结构实施的冲动。大型结构最重要的贡献在于概念上的连贯性,以及对领域的深刻洞察。每条结构规则都应该让开发变得更加容易。
So you have to fight the temptation to build frameworks and regiment the implementation of the large-scale structure. The most important contribution of the large-scale structure is conceptual coherence, and giving insight into the domain. Each structural rule should make development easier.
在如今行业正努力摆脱过度前期设计的时代,有些人会将大规模架构视为瀑布式架构这种糟糕旧时代的倒退。但事实上,找到有效架构的唯一途径是对领域和问题有非常深刻的理解,而实现这种理解的实用方法就是迭代开发过程。
In an era when the industry is shaking off excessive up-front design, some will see large-scale structure as a throwback to the bad old days of waterfall architecture. But in fact, the only way a useful structure can be found is from a very deep understanding of the domain and the problem, and the practical way to that understanding is an iterative development process.
致力于“演进式秩序”的团队必须勇于在整个项目生命周期中重新思考大规模的组织结构。团队不应固守早期构想的组织结构,因为那时人们对领域和需求的理解还不够透彻。
A team committed to EVOLVING ORDER must fearlessly rethink the large-scale structure throughout the project life cycle. The team should not saddle itself with a structure conceived of early on, when no one understood the domain or the requirements very well.
遗憾的是,这种演进过程意味着你最终的架构在开始时无法直接使用,因此你需要逐步重构以强制实现最终架构。这可能既费时又费力,但却是必要的。有一些通用的方法可以控制成本并最大化收益。
Unfortunately, that evolution means that your final structure will not be available at the start, and that means that you will have to refactor to impose it as you go along. This can be expensive and difficult, but it is necessary. There are some general ways of controlling the cost and maximizing the gain.
降低成本的关键之一是保持结构简单轻便。不要试图面面俱到,只需解决最关键的问题,其余问题则根据具体情况逐一处理。
One key to keeping the cost down is to keep the structure simple and lightweight. Don’t attempt to be comprehensive. Just address the most serious concerns and leave the rest to be handled on a case-by-case basis.
在初期阶段,选择一个较为宽松的结构会很有帮助,例如系统隐喻或几个责任层级。即使是极简的、宽松的结构也能提供轻量级的指导原则,有助于防止混乱。
Early on, it can be helpful to choose a loose structure, such as a SYSTEM METAPHOR or a couple of RESPONSIBILITY LAYERS. A minimal, loose structure can nonetheless provide lightweight guidelines that will help prevent chaos.
整个团队在新开发和重构过程中都必须遵循既定的结构。为此,整个团队必须理解这种结构。术语和关系必须纳入通用语言中。
The entire team must follow the structure in new development and refactoring. To do this, the structure must be understood by the entire team. The terminology and relationships must enter the UBIQUITOUS LANGUAGE.
大型结构可以为项目提供一套系统层面的通用术语,并使不同人员能够独立做出协调一致的决策。但由于大多数大型结构都只是较为宽泛的概念性指导原则,因此团队必须保持高度的自律。
Large-scale structure can provide a vocabulary for the project to deal with the system broadly, and for different people independently to make harmonious decisions. But because most large-scale structures are loose conceptual guidelines, the teams must exercise self-discipline.
如果参与人员不能始终如一地遵守结构,结构就容易失效。结构与模型或实现的具体部分之间的关系通常不会在代码中明确体现,功能测试也不依赖于结构。此外,结构往往比较抽象,因此很难在大型团队(或多个团队)中保持应用的一致性。
Without consistent adherence by the many people involved, structures have a tendency to decay. The relationship of the structure to detailed parts of the model or implementation is not usually explicit in the code, and functional tests do not rely on the structure. Plus, the structure tends to be abstract, so that consistency of application can be difficult to maintain across a large team (or multiple teams).
大多数团队内部的对话不足以在系统中维持一个统一的、大规模的结构。至关重要的是,要将这种结构融入到项目的通用语言中,并且让每个人都坚持不懈地运用这种语言。
The kinds of conversations that take place on most teams are not enough to maintain a consistent large-scale structure in a system. It is critical to incorporate it into the UBIQUITOUS LANGUAGE of the project, and for everyone to exercise that language relentlessly.
其次,任何结构上的改动都可能导致大量的重构工作。随着系统复杂性的增加和理解的加深,系统结构也在不断演变。每次结构发生变化,整个系统都必须进行相应的调整以适应新的秩序。显然,这需要耗费大量的工作。
Second, any change to the structure may lead to a lot of refactoring. The structure is evolving as system complexity increases and understanding deepens. Each time the structure changes, the entire system has to be changed to adhere to the new order. Obviously that is a lot of work.
这听起来可能很糟糕,但实际情况并非如此。我观察到,具有大型结构的设计通常比没有大型结构的设计更容易改造。即使是从一种结构类型转换到另一种结构类型,例如从“隐喻”(METAPHOR )结构转换为“层次”(LAYERS)结构,似乎也是如此。我无法完全解释其中的原因。部分原因是,当你能够理解事物当前的结构时,重新排列就更容易,而原有的结构使这一点更容易理解。部分原因是,维护先前结构所需的严谨性渗透到系统的各个方面。但我认为还有更深层次的原因,因为改变一个曾经拥有两种结构的系统就更加容易了。
This isn’t quite as bad as it sounds. I’ve observed that a design with a large-scale structure is usually much easier to transform than one without. This seems to be true even when changing from one kind of structure to another, say from METAPHOR to LAYERS. I can’t entirely explain this. Part of the answer is that it is easier to rearrange something when you can understand its current arrangement, and the preexisting structure makes that easier. Partly it is that the discipline that it took to maintain the earlier structure permeates all aspects of the system. But there is something more, I think, because it is even easier to change a system that has had two previous structures.
新皮夹克刚开始穿起来很硬,不太舒服,但穿了一天之后,肘部弯曲了几次,就变得灵活多了。弯曲。几次穿着之后,肩部变得宽松,夹克也更容易穿脱。几个月后,皮革变得柔软,穿着舒适,活动自如。模型似乎也是如此,它们通过不断的合理调整而反复演变。不断增长的知识被融入其中,主要的变革轴心已被识别并变得灵活,而稳定的方面则被简化。底层领域的更广泛的概念轮廓正在模型结构中逐渐显现。
A new leather jacket is stiff and uncomfortable, but after the first day of wear the elbows have flexed a few times and are becoming easier to bend. After a few more wearings, the shoulders have loosened up, and the jacket is easier to put on. After months of wear, the leather becomes supple and is comfortable and easy to move in. So it seems to be with models that are transformed repeatedly with sound transformations. Ever-increasing knowledge is embedded into them and the principal axes of change have been identified and made flexible, while stable aspects have been simplified. The broader CONCEPTUAL CONTOURS of the underlying domain are emerging in the model structure.
另一个应该应用于该模型的关键因素是连续精炼。这降低了以各种方式改变结构的难度。首先,通过从核心域中移除机制、通用子域和其他支持结构,可能需要重构的内容就会减少。
Another crucial force that should be applied to the model is continuous distillation. This reduces the difficulty of changing the structure in various ways. First, by removing mechanisms, GENERIC SUBDOMAINS, and other support structure from the CORE DOMAIN, there may simply be less to restructure.
如果可能,这些辅助元素应以简洁的方式融入大型架构。例如,在责任层系统中,通用子域可以定义为仅位于一个层级内。在可插拔组件中,通用子域可以完全由单个组件拥有,也可以作为一组相关组件共享的内核。这些辅助元素可能需要重构才能在架构中找到合适的位置;但它们独立于核心域,且往往更专注于特定领域,因此更容易实现。最终,它们的重要性较低,因此无需进行过多的细化。
If possible, these supporting elements should be defined to fit into the large-scale structure in a simple way. For example, in a system of RESPONSIBILITY LAYERS, a GENERIC SUBDOMAIN could be defined in such a way that it would fit within a single layer. With PLUGGABLE COMPONENTS, a GENERIC SUBDOMAIN could be owned entirely by a single component, or it could be a SHARED KERNEL among a set of related components. These supporting elements may have to be refactored to find their place in the structure; but they move independently of the CORE DOMAIN, and tend to be more narrowly focused, which makes it easier. And ultimately they are less critical, so refinement matters less.
提炼和重构的原则,即使是针对大型架构本身,也能帮助人们获得更深刻的理解。例如,最初选择架构层可能基于对领域的浅层理解;之后,这些浅层理解会逐渐被更深层次的抽象所取代,从而更好地表达系统的基本职责。这种清晰的思路使人们能够深入理解设计,这正是我们追求的目标。同时,这也是实现目标的手段之一,因为它使得大规模系统的操作更加便捷安全。
The principles of distillation and refactoring toward deeper insight apply even to the large-scale structure itself. For example, the layers may initially be chosen based on a superficial understanding of the domain; they are gradually replaced with deeper abstractions that express the fundamental responsibilities of the system. This sharpedged clarity lets people see deep into the design, which is the goal. It is also part of the means, as it makes manipulation of the system on a large scale easier and safer.
前三章介绍了许多领域驱动型战略设计的原则和技巧。在一个大型复杂系统中,你可能需要将其中几种原则和技巧应用于同一个设计中。大型结构如何与情境图共存?各个构建模块应该如何定位?第一步、第二步、第三步分别是什么?你如何着手制定战略?
The preceding three chapters presented many principles and techniques for domain-driven strategic design. In a large, complex system, you may need to bring several of them to bear on the same design. How does a large-scale structure coexist with a CONTEXT MAP? Where do the building blocks fit in? What do you do first? Second? Third? How do you go about devising your strategy?
图 17.1
Figure 17.1
战略设计的三个基本原则(背景、提炼和宏观结构)并非相互替代,而是相辅相成,并以多种方式相互作用。例如,宏观结构可以存在于一个特定的背景中,也可以跨越多个背景,并构建背景图。
The three basic principles of strategic design (context, distillation, and large-scale structure) are not substitutes for each other; they are complementary and interact in many ways. For example, a large-scale structure can exist within one BOUNDED CONTEXT, or it can cut across many of them and organize the CONTEXT MAP.
之前的责任层示例都局限于一个有界上下文。这是解释该概念最简单的方式,也是该模式的常见用法。在这种简单的场景中,层名称的含义仅限于该上下文,模型元素或子系统接口的名称也仅限于该上下文。
The previous examples of RESPONSIBILITY LAYERS were confined to one BOUNDED CONTEXT. This is the easiest way to explain the idea, and it’s a common use of the pattern. In such a simple scenario, the meanings of layer names are restricted to that CONTEXT, as are the names of model elements or subsystem interfaces that exist within that CONTEXT.
图 17.2. 在单个有界上下文中构建模型
Figure 17.2. Structuring a model within a single BOUNDED CONTEXT
这种局部结构在非常复杂但统一的模型中非常有用,提高了单个有界上下文中可以维护的复杂性上限。
Such a local structure can be useful in a very complicated but unified model, raising the complexity ceiling on how much can be maintained in a single BOUNDED CONTEXT.
但在许多项目中,更大的挑战在于理解各个不同部分如何协同运作。它们可能被划分到不同的上下文中,但每个上下文在整个集成系统中扮演什么角色?各个部分之间又是如何关联的?这时,就可以利用宏观结构来构建上下文图。在这种情况下,结构中的术语适用于整个项目(或至少是其中某个界限清晰的部分)。
But on many projects, the greater challenge is to understand how disparate parts fit together. They may be partitioned into separate CONTEXTS, but what part does each play in the whole integrated system and how do the parts relate to each other? Then the large-scale structure can be used to organize the CONTEXT MAP. In this case, the terminology of the structure applies to the whole project (or at least some clearly bounded part of it).
图 17.3. 不同边界上下文中各组成部分之间关系所施加的结构
Figure 17.3. Structure imposed on the relationships of components of distinct BOUNDED CONTEXTS
假设您想采用责任层架构,但您现有的遗留系统组织结构与您期望的大规模架构不符。您是否必须放弃责任层架构?答案是否定的,但您必须明确遗留系统在架构中的实际位置。事实上,对遗留系统进行特性描述可能有所帮助。遗留系统提供的服务可能实际上仅限于少数几个责任层。能够简洁地描述遗留系统所属的责任层,就能概括其范围和作用的关键方面。
Suppose you want to adopt RESPONSIBILITY LAYERS, but you have a legacy system whose organization is inconsistent with your desired large-scale structure. Do you have to give up your LAYERS? No, but you have to acknowledge the actual place the legacy has within the structure. In fact, it may help to characterize the legacy. The SERVICES the legacy provides may in fact be confined to only a few LAYERS. To be able to say that the legacy system fits within particular RESPONSIBILITY LAYERS concisely describes a key aspect of its scope and role.
图 17.4. 允许某些组件跨越层的结构
Figure 17.4. A structure that allows some components to span layers
如果通过FACADE访问传统子系统的功能,您可以将FACADE提供的每个SERVICE设计成适合一个层。
If the legacy subsystem’s capabilities are being accessed through a FACADE, you may be able to design each SERVICE offered by the FACADE to fit within one layer.
在本例中,由于“运输协调”应用程序是遗留系统,其内部结构呈现为一个未加区分的整体。但是,对于一个拥有完善的、覆盖整个上下文图的大型项目结构的项目团队来说,他们可以在自己的上下文中,选择按照熟悉的“层”来对模型进行排序。
The interior of the Shipping Coordination application, being a legacy in this example, is presented as an undifferentiated mass. But a team on a project with a well-established large-scale structure spanning the CONTEXT MAP could choose, within their CONTEXT, to order their model by the same familiar LAYERS.
图 17.5. 相同的结构应用于上下文内部以及整个上下文地图。
Figure 17.5. The same structure applied within a CONTEXT and across the CONTEXT MAP as a whole
当然,由于每个有界上下文都是独立的命名空间,因此可以使用一个结构来组织一个上下文内的模型,而另一个结构则用于相邻的上下文,甚至还有一个结构用于组织上下文映射。然而,如果过度依赖这种方式,可能会削弱大型结构作为项目统一概念集的价值。
Of course, because each BOUNDED CONTEXT is its own name space, one structure could be used to organize the model within one CONTEXT, while another was used in a neighboring CONTEXT, and still another organized the CONTEXT MAP. However, going too far down that path can erode the value of the large-scale structure as a unifying set of concepts for the project.
大规模结构和提炼的概念也相辅相成。大规模结构有助于解释核心域内部以及通用子域之间的关系。
The concepts of large-scale structure and distillation also complement each other. The large-scale structure can help explain the relationships within the CORE DOMAIN and between GENERIC SUBDOMAINS.
图 17.6.核心域(粗体)和通用子域的模块通过层级结构得以阐明。
Figure 17.6. MODULES of the CORE DOMAIN (in bold) and GENERIC SUBDOMAINS are clarified by the layers.
同时,大规模架构本身可能就是核心领域的重要组成部分。例如,区分潜在功能、运营功能、策略功能和决策支持功能的层次结构,可以提炼出对软件所解决的业务问题至关重要的洞见。如果一个项目被划分成许多限界上下文,导致核心领域的模型对象在项目的大部分范围内失去意义,那么这种洞见就显得尤为重要。
At the same time, the large-scale structure itself may be an important part of the CORE DOMAIN. For example, distinguishing the layering of potential, operations, policy, and decision support distills an insight that is fundamental to the business problem addressed by the software. This insight is especially useful if a project is carved up into many BOUNDED CONTEXTS, so that the model objects of the CORE DOMAIN don’t have meaning over much of the project.
在进行项目战略设计时,需要从对现状进行清晰的评估开始。
When you are tackling strategic design on a project, you need to start from a clear assessment of the current situation.
1.绘制情境图。你能绘制出一张前后一致的情境图吗?还是存在一些模棱两可的情况?
1. Draw a CONTEXT MAP. Can you draw a consistent one, or are there ambiguous situations?
2.注意项目中的语言使用。是否存在通用语言?该语言是否足够丰富,能够促进项目开发?
2. Attend to the use of language on the project. Is there a UBIQUITOUS LANGUAGE? Is it rich enough to help development?
3.理解重点是什么。核心领域是否已确定?是否有领域愿景陈述?你能写一份吗?
3. Understand what is important. Is the CORE DOMAIN identified? Is there a DOMAIN VISION STATEMENT? Can you write one?
4.该项目的技术是有利于还是不利于模型驱动设计?
4. Does the technology of the project work for or against a MODEL-DRIVEN DESIGN?
5.团队中的开发人员是否具备必要的技术技能?
5. Do the developers on the team have the necessary technical skills?
6.开发人员是否了解该领域?他们是否对该领域感兴趣?
6. Are the developers knowledgeable about the domain? Are they interested in the domain?
当然,你不可能找到完美的答案。你现在对这个项目的了解远不及将来。但这些问题能为你提供一个坚实的起点。当你对这些问题有了初步的具体答案时,你就会开始洞察哪些工作最迫切需要完成。随着时间的推移,你可以不断完善这些答案——尤其是情境图、领域愿景陈述以及你创建的其他任何文档——以反映情况的变化和新的见解。
You won’t find perfect answers, of course. You know less about this project right now than you ever will in the future. But these questions give you a solid starting point. By the time you have specific initial answers to these questions, you’ll have started getting insight into what most urgently needs to be done. As time goes along, you can refine the answers—especially the CONTEXT MAP, DOMAIN VISION STATEMENT, and any other artifacts you’ve created—to reflect changed situations and new insights.
传统上,架构是由一个在组织中比应用开发团队更有权力的团队在应用开发开始之前制定并传承下来的。但这并非必须如此。而且,这种方式通常效果不佳。
Traditionally, architecture is handed down, created before application development begins, by a team that has more power in the organization than the application development team. But it doesn’t have to be that way. That way doesn’t usually work very well.
战略设计,顾名思义,必须贯穿整个项目。项目组织方式多种多样,我不想过于死板。然而,任何有效的决策过程都需要一些基本要素。
Strategic design, by definition, must apply across the project. There are many ways to organize a project, and I don’t want to be too prescriptive. However, for any decision-making process to be effective, some fundamentals are required.
首先,让我们快速看一下我在实践中看到的两种有价值的风格(因此忽略了旧的“来自上天的智慧”风格)。
First, let’s take a quick look at two styles that I’ve seen provide some value in practice (thus ignoring the old “wisdom-from-on-high” style).
一支由沟通能力极强的人组成的自律团队,可以在没有中央权威的情况下运作,并遵循不断演进的秩序,从而达成一套共同的原则,使秩序有机地发展,而不是通过法令强制执行。
A self-disciplined team made up of very good communicators can operate without central authority and follow EVOLVING ORDER to arrive at a shared set of principles, so that order grows organically, not by fiat.
这是极限编程团队的典型模式。理论上,团队结构可以完全由任何一对程序员的灵感自发形成。但更常见的情况是,由团队中的某个人或部分成员负责统筹全局结构,有助于保持结构的统一性。如果这位非正式领导者本身就是一位实战型开发者——既是决策者又是沟通者,而非唯一的创意来源,那么这种方法尤其有效。在我所见过的极限编程团队中,这种战略性的设计领导似乎都是自发产生的,通常由教练担任。无论这位天然的领导者是谁,他/她仍然是开发团队的一员。因此,开发团队中至少需要有几位具备相应能力的成员来做出影响整个项目的设计决策。
This is the typical model for an Extreme Programming team. In theory, the structure may emerge completely spontaneously from the insight of any programming pair. More often, having an individual or a subset of the team with some oversight responsibility for large-scale structure helps keep the structure unified. This approach works well particularly if such an informal leader is a hands-on developer—an arbiter and communicator, and not the sole source of ideas. On the Extreme Programming teams I have seen, such strategic design leadership seems to have emerged spontaneously, often in the person of the coach. Whoever this natural leader is, he or she is still a member of the development team. It follows that the development team must have at least a few people of the caliber to make design decisions that are going to affect the whole project.
当一个大型架构跨越多个团队时,关系密切的团队可能会开始非正式合作。在这种情况下,每个应用团队仍然会进行探索,最终形成大型架构的构想,但具体的方案会由一个由各团队代表组成的非正式委员会进行讨论。在评估设计方案的影响后,参与者可以决定采纳、修改或暂缓执行。在这种松散的合作关系中,各团队会努力协同推进。这种安排适用于团队数量相对较少、所有团队都致力于相互协调、设计能力相当,以及结构需求足够相似,能够通过单一的大型架构来满足的情况。
When a large-scale structure spans multiple teams, closely affiliated teams may begin to collaborate informally. In such a situation, each application team still makes the discoveries that lead to the idea for a large-scale structure, but then particular options are discussed by the informal committee, made up of representatives of the various teams. After assessing the impact of the design, participants may decide to adopt it, modify it, or leave it on the table. The teams attempt to move together in this loose affiliation. This arrangement can work when there are relatively few teams, when they are all committed to coordinating with each other, when their design capabilities are comparable, and when their structural needs are similar enough to be met by a single large-scale structure.
当策略需要在多个团队之间共享时,决策的某种程度的集中化似乎很有吸引力。象牙塔架构师那种失败的模式并非唯一选择。架构团队可以与各个应用团队并肩工作,帮助协调和统一他们的大型架构、有界上下文边界以及其他跨团队的技术问题。为了发挥这种作用,他们必须具备以应用开发为中心的思维模式。
When a strategy will be shared among several teams, some centralization of decision making does seem attractive. The failed model of the ivory tower architect is not the only possibility. An architecture team can act as a peer with various application teams, helping to coordinate and harmonize their large-scale structures as well as BOUNDED CONTEXT boundaries and other cross-team technical issues. To be useful in this, they must have a mind set that emphasizes application development.
从组织结构图上看,这个团队可能与传统的架构团队并无二致,但实际上,他们在每一项活动中都截然不同。团队成员与开发人员紧密合作,共同探索模式,与不同团队进行实验以提炼精华,并亲力亲为地参与实际工作。
On an organization chart, this team may look just like the traditional architecture team, but it is actually different in every activity. Team members are true collaborators with development, discovering patterns along with the developers, experimenting with various teams to reach distillations, and getting their hands dirty.
我见过几次这种情况,一个项目最终会有一个首席架构师,他会做以下列表中的大部分事情。
I have seen this scenario a couple of times, when a project ends up with a lead architect who does most of the things on the following list.
显然,如果并非所有人都了解并遵循该策略,那么它就毫无意义。这一要求促使人们围绕拥有官方“权威”的中心化架构团队进行组织——以便所有地方都能适用相同的规则。讽刺的是,象牙塔里的架构师往往被忽视或绕过。当架构师缺乏将自身规则应用于实际应用的实践反馈,导致方案不切实际时,开发人员别无选择。
Obviously, if everyone doesn’t know the strategy and follow it, it is irrelevant. This requirement leads people to organize around centralized architecture teams with official “authority”—so that the same rules will be applied everywhere. Ironically, ivory tower architects are often ignored or bypassed. Developers have no choice when the architects’ lack of feedback from hands-on attempts to apply their own rules to real applications results in impractical schemes.
在一个沟通非常顺畅的项目中,应用团队制定的战略设计方案可能更有效地惠及所有人。该战略方案不仅切合实际,而且具备了集体决策应有的权威性。
On a project with very good communication, a strategic design that emerges from the application team may actually reach everyone more effectively. The strategy will be relevant, and it will have the authority that attaches to intelligent community decisions.
无论采用何种系统,与其关注管理层赋予的权力,不如关注开发人员与战略之间的实际关系。
Whatever the system, be less concerned with the authority bestowed by management than with the actual relationship the developers have with the strategy.
创建组织原则、构建大规模架构,或提炼此类微妙之处,都需要对项目需求和领域概念有非常深刻的理解。只有应用开发团队成员才具备这种深度知识。这就解释了为什么架构团队创建的应用架构很少能真正发挥作用,尽管许多架构师才华横溢。
Creating an organizing principle, large-scale structure, or distillation of such subtlety requires a really deep understanding of the needs of the project and the concepts of the domain. The only people who have that depth of knowledge are the members of the application development team. This explains why application architectures created by architecture teams are so seldom helpful, despite the undeniable talent of many of the architects.
与技术基础设施和架构不同,战略设计本身并不涉及编写大量代码,尽管它会影响所有开发工作。它需要的是与应用开发团队的密切合作。经验丰富的架构师能够倾听来自各个团队的想法,并促进通用解决方案的开发。
Unlike technical infrastructure and architectures, strategic design does not itself involve writing a lot of code, although it influences all development. What it does require is involvement with the application development teams. An experienced architect may be able to listen to ideas coming from various teams and facilitate the development of a generalized solution.
我曾合作过的一个技术架构团队,会安排团队成员轮流到各个尝试使用其框架的应用开发团队中。这种轮岗方式让架构团队能够亲身体验开发人员面临的挑战,同时也让他们掌握了如何运用框架的各种技巧。战略设计同样需要这种紧密的反馈机制。
One technical architecture team I worked with actually circulated its own members through the various application development teams that were attempting to use its framework. This rotation pulled into the architecture team the hands-on experience of the challenges facing the developers, while it simultaneously transferred the knowledge of how to apply the subtleties of the framework. Strategic design has this same need of a tight feedback loop.
高效的软件开发是一个高度动态的过程。当最高层决策一成不变时,团队在应对变化时选择余地就会减少。“演进式秩序”通过强调根据不断加深的理解对大规模结构进行持续调整,从而避免了这一陷阱。
Effective software development is a highly dynamic process. When the highest level of decisions is set in stone, the team has fewer options when it must respond to change. EVOLVING ORDER avoids this trap by emphasizing ongoing change to the large-scale structure in response to deepening insight.
当太多设计决策被预先设定时,开发团队就会受到束缚,缺乏解决实际问题的灵活性。因此,虽然协调原则很有价值,但它必须随着开发项目的持续进行而发展和变化,并且不应剥夺应用程序开发人员过多的权力,因为他们的工作本身就已经非常艰巨了。
When too many design decisions are preordained, the development team can be hobbled, without the flexibility to solve the problems they are charged with. So, while a harmonizing principle can be valuable, it must grow and change with the ongoing life of the development project, and it must not take too much power away from the application developers, whose job is hard enough as it is.
在强烈的反馈下,创新会在构建应用程序的过程中遇到障碍以及发现意想不到的机会时涌现。
With strong feedback, innovations emerge as obstacles are encountered in building applications and as unexpected opportunities are discovered.
这个级别的设计需要精湛的技艺,而这种技艺目前可能比较稀缺。管理者往往会将技术最精湛的开发人员调到架构团队和基础设施团队,因为他们希望充分利用这些高级设计师的技能。而开发人员则被这种能够产生更广泛影响或解决“更有趣”问题的机会所吸引。此外,成为精英团队的一员也具有很高的声望。
Design at this level calls for sophistication that is probably in short supply. Managers tend to move the most technically talented developers into architecture teams and infrastructure teams, because they want to leverage the skills of these advanced designers. For their part, the developers are attracted to the opportunity to have a broader impact or to work on “more interesting” problems. And there is prestige attached to being a member of an elite team.
这些因素往往导致最终只剩下技术水平最低的开发人员来构建应用程序。但构建优秀的应用程序需要设计能力;这种情况注定会失败。即使战略团队制定了出色的战略设计,应用程序团队也可能缺乏执行该设计所需的专业技能。
These forces often leave behind only the least technically sophisticated developers to actually build applications. But building good applications takes design skill; this is a setup for failure. Even if a strategy team creates a great strategic design, the application team won’t have the design sophistication to follow it.
相反,这类团队几乎从不包含那些设计能力或许稍弱,但在该领域拥有最丰富经验的开发人员。战略设计并非纯粹的技术任务;如果架构师与拥有深厚领域知识的开发人员脱节,只会进一步阻碍他们的工作。而且,领域专家也必不可少。
Conversely, such teams almost never include the developer who perhaps has weaker design skills but who has the most extensive experience in the domain. Strategic design is not a purely technical task; cutting themselves off from developers with deep domain knowledge hobbles the architects’ efforts further. And domain experts are needed too.
所有应用团队都必须配备优秀的设计师。任何试图进行战略设计的团队都必须具备领域知识。或许只需聘请更多资深设计师即可。将架构团队改为兼职模式也可能有所帮助。我相信有很多行之有效的方法,但任何高效的战略团队都必须与高效的应用团队紧密合作。
It is essential to have strong designers on all application teams. It is essential to have domain knowledge on any team attempting strategic design. It may simply be necessary to hire more advanced designers. It may help to keep architecture teams part-time. I’m sure there are many ways that work, but any effective strategy team has to have as a partner an effective application team.
精简和极简主义是任何优秀设计作品的基石,但对于战略设计而言,极简主义更为关键。哪怕是最细微的不匹配都可能造成严重的阻碍。独立的架构团队尤其需要谨慎,因为他们对可能给应用团队带来的障碍缺乏足够的感知。同时,架构师们对自身主要职责的热情也容易让他们过度设计。我见过很多次这种情况,甚至我自己也犯过同样的错误。一个好想法会引发另一个好想法,最终导致架构过度构建,反而适得其反。
Distillation and minimalism are essential to any good design work, but minimalism is even more critical for strategic design. Even the slightest ill fit has a terrible potential for getting in the way. Separate architecture teams have to be especially careful because they have less feel for the obstacles they might be placing in front of application teams. At the same time, the architects’ enthusiasm for their primary responsibility makes them more likely to get carried away. I’ve seen this phenomenon many times, and I’ve even done it. One good idea leads to another, and we end up with an overbuilt architecture that is counterproductive.
相反,我们必须约束自己,制定精简的组织原则和核心模型,确保其中不包含任何对设计清晰度没有显著提升作用的内容。事实上,几乎所有东西都会造成阻碍,所以每个元素都必须物有所值。意识到你最好的想法很可能会妨碍他人,这需要谦逊。
Instead, we have to discipline ourselves to produce organizing principles and core models that are pared down to contain nothing that does not significantly improve the clarity of the design. The truth is, almost everything gets in the way of something, so each element had better be worth it. Realizing that your best idea is likely to get in somebody’s way takes humility.
优秀对象设计的精髓在于赋予每个对象清晰明确的职责,并将相互依赖性降至最低。有时,我们试图让团队内部的交互像软件开发中那样井然有序。然而,一个优秀的项目中往往充斥着各种各样的人,他们互相插手彼此的工作。开发人员研究框架,架构师编写应用代码,每个人都互相交流。这种混乱的局面虽然高效,却也暗藏玄机。应该让对象专注于特定领域,而让开发人员成为通才。
The essence of good object design is to give each object a clear and narrow responsibility and to reduce interdependence to an absolute minimum. Sometimes we try to make interactions on teams as tidy as they should be in our software. A good project has lots of people sticking their nose in other people’s business. Developers play with frameworks. Architects write application code. Everyone talks to everyone. It is efficiently chaotic. Make the objects into specialists; let the developers be generalists.
为了帮助大家更清楚地了解各项任务,我区分了战略设计和其他类型的设计。因此,我必须指出,两种设计活动并不意味着需要两种类型的人员。基于深度模型创建灵活的设计是一项高级设计活动,但细节至关重要,必须由负责代码编写的人员来完成。战略设计源于应用设计,但它需要对活动有全局性的把握,而且可能需要跨多个团队协作。人们总是喜欢把任务拆分,这样设计专家就不必了解业务,领域专家也不必了解技术。个人的学习能力是有限的,但过度专业化会削弱领域驱动型设计的动力。
Because I’ve made the distinction between strategic design and other kinds of design to help clarify the tasks involved, I must point out that having two kinds of design activity does not mean having two kinds of people. Creating a supple design based on a deep model is an advanced design activity, but the details are so important that it has to be done by someone working with the code. Strategic design emerges out of application design, yet it requires a big-picture view of activity, possibly spanning multiple teams. People love to find ways to chop up tasks so that design experts don’t have to know the business and domain experts don’t have to understand technology. There is a limit to how much an individual can learn, but overspecialization takes the steam out of domain-driven design.
技术框架可以通过提供一个基础设施层,使应用程序无需实现基本服务,并帮助将领域与其他关注点隔离,从而极大地加速应用程序开发,包括领域层。但架构也存在干扰领域模型表达性实现和易于变更的风险。即使在以下情况下,这种情况也可能发生:框架设计者原本无意涉足领域层或应用层。
Technical frameworks can greatly accelerate application development, including the domain layer, by providing an infrastructure layer that frees the application from implementing basic services, and by helping to isolate the domain from other concerns. But there is a risk that an architecture can interfere with expressive implementations of the domain model and easy change. This can happen even when the framework designers had no intention of venturing into the domain or application layers.
那些能够限制战略设计弊端的相同理念,同样也适用于技术架构。演进、极简主义以及与应用开发团队的密切合作,能够不断完善服务和规则,真正帮助应用开发,而不会阻碍其进行。不遵循这些原则的架构,要么会扼杀应用开发的创造力,要么会被绕过,最终导致应用开发实际上没有任何架构支撑。
The same biases that limit the downside of strategic design can help with technical architecture. Evolution, minimalism, and involvement with the application development team can lead to a continuously refined set of services and rules that genuinely help application development without getting in the way. Architectures that don’t follow this path will either stifle the creativity of application development or will find their architecture circumvented, leaving application development, for practical purposes, with no architecture at all.
有一种态度肯定会破坏一个框架。
There is one particular attitude that will surely ruin a framework.
团队划分若假定部分开发人员不够聪明,无法进行设计,则很可能失败,因为这种划分低估了应用程序开发的难度。如果这些人不够聪明,无法进行设计,就不应该让他们参与软件开发。如果他们足够聪明,那么试图迁就他们只会阻碍他们获得所需的工具。
Team divisions that assume some developers are not smart enough to design are likely to fail because they underestimate the difficulty of application development. If those people are not smart enough to design, they shouldn’t be assigned to develop software. If they are smart enough, then the attempts to coddle them will only put up barriers between them and the tools they need.
这种态度也会破坏团队之间的关系。我曾经加入过一些傲慢的团队,发现自己每次和开发人员谈话都要道歉,为自己的这种处境感到尴尬。(恐怕我从未能改变过这样的团队。)
This attitude also poisons the relationship between teams. I’ve ended up on arrogant teams like this and found myself apologizing to developers in every conversation, embarrassed by my association. (I’ve never managed to change such a team, I’m afraid.)
现在,封装无关的技术细节与我所批评的那种预打包完全不同。框架可以将强大的抽象和工具交到开发者手中,让他们摆脱繁琐的工作。很难用概括的方式来描述其中的区别,但你可以通过询问框架设计者对工具/框架/组件使用者的期望来判断。如果设计者对框架用户表现出高度的尊重,那么他们很可能走对了方向。
Now, encapsulating irrelevant technical detail is completely different from the kind of prepackaging I’m disparaging. A framework can place powerful abstractions and tools in developers’ hands and free them from drudgery. It is hard to describe the difference in a generalized way, but you can tell the difference by asking the framework designers what they expect of the person who will be using the tool/framework/components. If the designers seem to have a high level of respect for the user of the framework, then they are probably on the right track.
以克里斯托弗·亚历山大为首的一群建筑师(设计实体建筑的建筑师)倡导分阶段发展。建筑和城市规划领域。他们很好地解释了总体规划失败的原因。
A group of architects (the kind who design physical buildings), led by Christopher Alexander, were advocates of piecemeal growth in the realm of architecture and city planning. They explained very nicely why master plans fail.
如果没有某种规划过程,俄勒冈大学绝不可能拥有像剑桥大学那样深厚和谐的秩序。
Without a planning process of some kind, there is not a chance in the world that the University of Oregon will ever come to possess an order anywhere near as deep and harmonious as the order that underlies the University of Cambridge.
总体规划一直是解决这一难题的传统方法。总体规划力求制定足够的指导原则,以确保整体环境的协调一致,同时又为单个建筑和开放空间根据当地需求进行调整留出空间。
The master plan has been the conventional way of approaching this difficulty. The master plan attempts to set down enough guidelines to provide for coherence in the environment as a whole—and still leave freedom for individual buildings and open spaces to adapt to local needs.
……未来这所大学的各个部分将构成一个连贯的整体,因为它们只是简单地插入到设计中的相应插槽中。
. . . and all the various parts of this future university will form a coherent whole, because they were simply plugged into the slots of the design.
……实际上,总体规划往往失败——因为它们建立的是极权秩序,而非有机秩序。它们过于僵化,难以适应社区生活中不可避免的自然且不可预测的变化。随着这些变化的发生……总体规划便会过时,不再被执行。即便总体规划在一定程度上得到执行……它们也未能充分阐明建筑之间的联系、人性化的尺度、功能的平衡等等,从而无法帮助每个具体的建筑和设计行为与整体环境和谐共存。
. . . in practice master plans fail—because they create totalitarian order, not organic order. They are too rigid; they cannot easily adapt to the natural and unpredictable changes that inevitably arise in the life of a community. As these changes occur . . . the master plan becomes obsolete, and is no longer followed. And even to the extent that master plans are followed . . . they do not specify enough about connections between buildings, human scale, balanced function, etc. to help each local act of building and design become well-related to the environment as a whole.
……试图引导这样的方向,就好比给孩子的涂色书涂色……充其量,由此产生的秩序也只是平庸之作。
. . . The attempt to steer such a course is rather like filling in the colors in a child’s coloring book . . . . At best, the order which results from such a process is banal.
因此,作为一种有机秩序的来源,总体规划既过于精确,又不够精确。整体过于精确,而细节则不够精确。
. . . Thus, as a source of organic order, a master plan is both too precise, and not precise enough. The totality is too precise: the details are not precise enough.
……总体规划的存在疏远了用户[因为,根据定义],社区成员对社区未来发展方向的影响很小,因为大多数重要决策已经做出。
. . . the existence of a master plan alienates the users [because, by definition] the members of the community can have little impact on the future shape of their community because most of the important decisions have already been made.
——摘自《俄勒冈实验》,第16-28页(Alexander等人,1975年)
—From The Oregon Experiment, pp. 16–28 (Alexander et al. 1975)
亚历山大和他的同事们则提倡所有社区成员在每次零散发展过程中应用一套原则,以便形成“有机秩序”,使其能够很好地适应环境。
Alexander and his colleagues advocated instead a set of principles for all community members to apply to every act of piecemeal growth, so that “organic order” emerges, well adapted to circumstances.
虽然参与前沿项目、尝试各种有趣的想法和工具令人很有成就感,但如果软件最终无法得到有效利用,对我来说,这一切都毫无意义。事实上,衡量软件成功的真正标准在于它能否经受住时间的考验。这些年来,我一直关注着我之前一些项目的进展。
Although it is very satisfying working on a cutting-edge project and experimenting with interesting ideas and tools, for me it is a hollow experience if the software does not find productive use. In fact, the true test of success is how the software serves over a period of time. I have been able to follow the stories of some of my former projects over the years.
我将在此讨论其中的五个项目,它们都认真尝试过领域驱动设计,当然,并非系统地采用这种方法,也并非以此命名。所有这些项目最终都交付了软件:有些项目成功地贯彻了模型驱动设计,而有些项目则偏离了正轨。一些应用程序持续发展和变化多年,而一个项目停滞不前,还有一个项目则很快夭折。
I’ll discuss here five of those, each of which made a serious attempt at domain-driven design, though not systematically and not by that name, of course. All of these projects did deliver software: some managed to carry through and produce a model-driven design, while one slipped off that track. Some of the applications continued to grow and change for many years, while one stagnated and one died young.
第一章中介绍的PCB设计软件在测试用户中大受欢迎。可惜的是,发起该项目的初创公司在市场营销方面彻底失败,最终倒闭。如今,只有少数PCB工程师在使用这款软件,他们保留了当年测试版的旧版本。就像任何一款无人问津的软件一样,只要与之集成的程序没有发生致命的变更,它就能继续运行。
The PCB design software described in Chapter 1 was a smash hit among beta users in the field. Unfortunately, the start-up company that had initiated the project utterly failed in its marketing function and was eventually euthanized. The software is now used by a handful of PCB engineers who have old copies they kept from the beta program. Like any orphan software, it will continue to work until there is some fatal change to one of the programs with which it is integrated.
第九章中讲述的贷款软件故事,在我所描述的突破之后,又沿着大致相同的轨迹蓬勃发展并演进了三年。之后,该项目被剥离出来,成为一家……这家公司是独立运营的。在这次重组的动荡中,从一开始就领导该项目的项目经理被解雇,一些核心开发人员也随之离开。新团队的设计理念略有不同,对对象建模的投入不如之前那么彻底。但他们保留了一个具有复杂行为的独立领域层,并继续重视开发团队的领域知识。分拆七年后,该软件仍在不断增强,新增了诸多功能。它已成为该领域的领先应用,服务于越来越多的客户机构,同时也是公司最大的收入来源。
The loan software whose story was told in Chapter 9 thrived and evolved along much the same track for three years after the breakthrough I wrote about. At that point, the project was spun off as an independent company. In the turmoil of this reorganization, the project manager who had led the project from the beginning was ejected, and some of the core developers left with him. The new team had a somewhat different design philosophy, not as fully committed to object modeling. But they retained a distinct domain layer with complex behavior and continued to value domain knowledge on the development team. Seven years after the spin-off, the software continues to be enhanced with new features. It is the leading application in its field and serves an increasing number of client institutions, as well as being the largest revenue stream for the company.
新栽种的橄榄树林
A Newly Planted Olive Grove
在领域驱动方法得到更广泛的应用之前,许多项目中那些有趣的软件会在一段短暂而高效的开发周期内完成。最终,项目会演变成更为传统的模式,可能无法充分利用,更遑论增强,之前提炼出的深度模型的强大功能。我当然希望看到更多,但真正能为用户带来多年持续价值的成功案例才是真正的成功。
Until the domain-driven approach is more widespread, the interesting software on many projects will be built in a short, highly productive interval. Eventually the project will transform into something more conventional that may not be able to fully exploit, much less enhance, the power of the deep models that were distilled earlier. I could wish for more, but truly those are successes that deliver sustained value to users over many years.
在一个项目中,我与另一位开发人员合作,编写了一个客户用于开发其核心产品的实用程序。这些功能相当复杂,并且以错综复杂的方式组合在一起。我很享受这个项目的工作,我们最终开发出了一个具有抽象核心的灵活设计。当这个软件交付出去后,所有最初参与开发的人员的工作也就结束了。因为它如此复杂……由于过渡突然,我原本预期支持组合元素的设计特性可能会令人困惑,并可能被更典型的用例逻辑所取代。但这种情况最初并没有发生。交接时,我们提供了一套完整的测试套件和一份提炼文档。新团队成员利用这份文档指导他们的探索,随着研究的深入,他们被该设计所展现的可能性所吸引。一年后,当我听到他们的反馈时,我意识到这种“通用语言”已经传递给了另一个团队,并得以延续发展。
On one project I paired with another developer to write a utility the customer needed to produce its core product. The features were fairly complicated and combined in intricate ways. I enjoyed the project work and we produced a supple design with an ABSTRACT CORE. When this software was handed off, that was the end of involvement for everyone who had initially developed it. Because it was such an abrupt transition, I expected that the design features which supported the combinable elements might be confusing and might get replaced by more typical case logic. This did not initially happen. When we handed off, the package included a thorough test suite and a distillation document. The new team members used that document to guide their explorations, and as they looked into things, they became excited by the possibilities the design presented. When I heard their comments a year later, I realized that the UBIQUITOUS LANGUAGE had sparked across to the other team and stayed alive, continuing to evolve.
七年后
Seven Years Later
又过了一年,我听到了另一个故事。团队遇到了新的需求,开发人员发现原有的设计根本无法满足这些需求。他们被迫对设计进行了大刀阔斧的修改。当我进一步了解情况时,我发现我们模型中的某些特性使得解决这些问题变得十分棘手。正是在这样的时刻,突破瓶颈、构建更深层次的模型往往成为可能,尤其是在像本案例中这样,开发人员已经积累了丰富的领域知识和经验的情况下。事实上,他们当时灵感迸发,最终基于这些新发现彻底改造了模型和设计。
Then, another year later, I heard a different story. The team had encountered new requirements that the developers didn’t see any way to accomplish within the inherited design. They had been forced to change the design almost beyond recognition. As I probed for more details, I could see that aspects of our model would have made solving those problems awkward. It is precisely during such moments when a breakthrough to a deeper model is often possible, especially when, as in this case, the developers had accumulated deep knowledge and experience in the domain. In fact, they had had a rush of new insights and ended up transforming the model and design based on those insights.
他们小心翼翼、委婉地跟我讲述了这件事,我想他们是预料到我会对他们放弃我这么多心血成果感到失望。但我对自己的设计并没有那么执着。一个设计的成功并不一定取决于它的静止不变。如果一个人们依赖的系统变得晦涩难懂,它就会永远成为不可撼动的“遗产”。一个深层的模型能够带来清晰的视野,从而产生新的洞见;而一个灵活的设计则能够促进持续的变革。他们提出的模型更加深入,也更贴合用户的实际需求。他们的设计解决了实际问题。软件的本质就是不断变化,而这个程序在它的团队手中也一直在持续演进。
They told me this story carefully, diplomatically, expecting, I suppose, that I would be disappointed by their discarding of so much of my work. I am not that sentimental about my designs. The success of a design is not necessarily marked by its stasis. Take a system people depend on, make it opaque, and it will live forever as untouchable legacy. A deep model allows clear vision that can yield new insight, while a supple design facilitates ongoing change. The model they came up with was deeper, better aligned with the real concerns of the users. Their design solved real problems. It is the nature of software to change, and this program has continued to evolve in the hands of the team that owns it.
书中穿插的航运案例大致取材于一家大型国际集装箱航运公司的项目。项目初期,领导层致力于领域驱动开发方法,但他们始终未能建立起能够完全支持这种方法的开发文化。几个设计技能和对象经验水平参差不齐的团队着手创建模块,该项目由团队领导之间的非正式合作以及以客户为中心的架构团队进行松散协调。我们确实开发了一个相当深入的核心领域模型,并且存在一种可行的通用语言。
The shipping examples scattered through the book are loosely based on a project for a major international container-shipping company. Early on, the leadership of the project was committed to a domain-driven approach, but they never produced a development culture that could fully support it. Several teams with widely different levels of design skill and object experience set out to create modules, loosely coordinated by informal cooperation between team leaders and by a customer-focused architecture team. We did develop a reasonably deep model of the CORE DOMAIN, and there was a viable UBIQUITOUS LANGUAGE.
但公司文化强烈抵制迭代开发,导致我们迟迟未能发布内部可用版本。因此,问题在后期才暴露出来,此时修复起来风险更大、成本更高。后来,我们发现模型中的某些特定方面导致了数据库性能问题。模型驱动设计的一个自然组成部分是将实现问题反馈到模型变更中,但当时人们普遍认为我们已经投入太多精力,无法更改基础模型。于是,我们修改了代码以提高效率,却削弱了代码与模型的关联。初始版本还暴露了技术基础设施的扩展性限制,这让管理层感到恐慌。我们引入了专家来解决基础设施问题,项目才得以恢复。但实现和领域建模之间的闭环始终没有闭合。
But the company culture fiercely resisted iterative development, and we waited far too long to push out a working internal release. Therefore, problems were exposed at a late stage, when they were more risky and expensive to fix. At some point, we discovered specific aspects of the model were causing performance problems in the database. A natural part of MODEL-DRIVEN DESIGN is the feedback from implementation problems to changes in the model, but by that time there was a perception that we were too far down the road to change the fundamental model. Instead, changes were made to the code to make it more efficient, and its connection to the model was weakened. The initial release also exposed scaling limitations in the technical infrastructure that threw a scare into management. Expertise was brought in to fix the infrastructure problems, and the project bounced back. But the loop was never closed between implementation and domain modeling.
一些团队交付了功能复杂、模型表达力强的优秀软件。另一些团队则交付了僵化的软件,将模型简化为数据结构,尽管他们也保留了普适语言的痕迹。或许上下文映射(CONTEXT MAP)能帮上大忙,因为各个团队的产出之间的关系杂乱无章。然而,普适语言中蕴含的核心模型最终确实帮助各团队将系统整合在一起。
A few teams delivered fine software with complex capabilities and expressive models. Others delivered stiff software that reduced the model to data structures, though even they retained traces of the UBIQUITOUS LANGUAGE. Perhaps a CONTEXT MAP would have helped us as much as anything, as the relationship between the output of the various teams was haphazard. Yet that CORE model carried in the UBIQUITOUS LANGUAGE did help the teams ultimately to glue together a system.
尽管规模有所缩减,但该项目替换了多个遗留系统。整个系统由一套共享的概念支撑,尽管大部分设计缺乏灵活性。多年后的今天,它本身也已基本固化为遗留系统,但仍然全天候为全球业务提供服务。尽管更成功的团队的影响力逐渐扩大,但即使在最富有的公司,时间最终也会耗尽。该项目的文化从未真正吸收模型驱动设计。如今,新的开发工作在不同的平台上进行,并且仅间接受到我们工作的影响——因为新的开发人员遵循的是他们所处的遗留系统。
Although reduced in scope, the project replaced several legacy systems. The whole was held together by a shared set of concepts, though most of the design was not very supple. It has itself largely fossilized into legacy now, years later, but it still serves the global business 24 hours a day. Although the more successful teams’ influence gradually spread, time runs out eventually, even in the richest company. The culture of the project never really absorbed MODEL-DRIVEN DESIGN. New development today is on different platforms and is only indirectly influenced by the work we did—as the new developers CONFORM to their legacy.
在某些圈子里,像这家航运公司最初设定的那种雄心勃勃的目标已经失去了信誉。似乎最好还是开发我们知道如何交付的小型应用程序。最好还是坚持采用最基本的设计标准来完成简单的任务。这种保守的方法自有其用武之地,它能够实现范围清晰、响应迅速的项目。但是,集成化的、模型驱动的系统能够带来这些拼凑式方案无法提供的价值。其实还有第三条路。领域驱动设计允许基于深层模型和灵活的设计,逐步构建功能丰富的大型系统。
In some circles, ambitious goals like those the shipping company initially set have been discredited. Better, it seems, to make little applications we know how to deliver. Better to stick to the lowest common denominator of design to do simple things. This conservative approach has its place, and allows for neatly scoped, quick-response projects. But integrated, model-driven systems promise value that those patchworks can’t. There is a third way. Domain-driven design allows piecemeal growth of big systems with rich functionality, by building on a deep model and supple design.
最后,我想介绍一下Evant,一家开发库存管理软件的公司。我在那里担任辅助角色,为公司已有的强大设计文化做出了贡献。其他人曾将这个项目誉为极限编程的典范,但人们通常忽略的是,该项目高度依赖领域驱动。他们不断提炼更深层次的模型,并将其转化为更加灵活的设计。这个项目一直蓬勃发展,直到2001年互联网泡沫破灭。之后,由于缺乏投资,公司规模缩减,软件开发几乎停滞,似乎即将走向终结。但在2002年夏天,全球十大零售商之一联系了Evant。这位潜在客户对产品很满意,但需要对设计进行修改,以使应用程序能够扩展到庞大的库存计划操作。这是Evant的最后机会。
I’ll close this list with Evant, a company that develops inventory management software, where I played a secondary supporting role and contributed to an already strong design culture. Others have written about this project as a poster child of Extreme Programming, but what is not usually remarked upon is that the project was intensely domain-driven. Ever deeper models were distilled and expressed in ever more supple designs. This project thrived until the “dot com” crash of 2001. Then, starved for investment funds, the company contracted, software development went mostly dormant, and it seemed that the end was near. But in the summer of 2002, Evant was approached by one of the top ten retailers in the world. This potential client liked the product, but it needed design changes to allow the application to scale up for an enormous inventory planning operation. It was Evant’s last chance.
尽管团队只剩下四名开发人员,但他们依然拥有诸多优势。他们技术精湛,精通领域知识,其中一位成员还拥有处理扩展性问题的丰富经验。他们拥有高效的开发文化,并且代码库设计灵活,便于变更。那年夏天,这四位开发人员付出了巨大的努力,最终开发出能够处理数十亿个规划元素和数百名用户的功能。凭借这些强大的能力,Evant 赢得了这家巨型客户,不久之后,又被另一家公司收购,后者希望利用他们的软件及其应对新需求的成熟能力。
Although reduced to four developers, the team had assets. They were skilled, with knowledge of the domain, and one member had expertise in scaling issues. They had a very effective development culture. And they had a code base with a supple design that facilitated change. That summer, those four developers made a heroic development effort resulting in the ability to handle billions of planning elements and hundreds of users. On the strength of those capabilities, Evant won the behemoth client and, soon after, was bought by another company that wanted to leverage their software and their proven ability to accommodate new demands.
领域驱动设计文化(以及极限编程文化)在转型过程中幸存下来并焕发了新的活力。如今,这种模型和设计仍在不断发展演进,比我两年前做出贡献时更加丰富和灵活。而且,Evant 的成员并没有被采购公司同化,而是继续保持着自身的独立性。这个团队似乎正在激励公司现有的项目团队效仿他们的做法。故事还没有结束。
The domain-driven design culture (as well as the Extreme Programming culture) survived the transition and was revitalized. Today, the model and design continue to evolve, far richer and suppler two years later than when I made my contribution. And rather than being assimilated into the purchasing company, the members of the Evant team seem to be inspiring the company’s existing project teams to follow their lead. This story isn’t over yet.
任何项目都不可能采用本书中的所有技巧。即便如此,任何致力于领域驱动设计的项目都会在某些方面具有可辨识性。其最显著的特征是优先理解目标领域,并将这种理解融入软件设计中。其他一切都由此前提展开。团队成员会注意项目语言的使用,并不断改进。他们对领域模型的质量要求很高,因为他们会持续学习更多关于领域的知识。他们将持续改进视为机遇,而将不合适的模型视为风险。他们非常重视设计技能,因为开发出能够清晰反映领域模型的生产级软件并非易事。他们会遇到各种障碍,但他们会坚持原则,重新振作,继续前进。
No project will ever employ every technique in this book. Even so, any project committed to domain-driven design will be recognizable in a few ways. The defining characteristic is a priority on understanding the target domain and incorporating that understanding into the software. Everything else flows from that premise. Team members are conscious of the use of language on the project and cultivate its refinement. They are hard to satisfy with the quality of the domain model, because they keep learning more about the domain. They see continuous refinement as an opportunity and an ill-fitting model as a risk. They take design skill seriously because it isn’t easy to develop production-quality software that clearly reflects the domain model. They stumble over obstacles, but they hold on to their principles as they pick themselves up and continue forward.
与物理或化学相比,天气、生态系统和生物学过去被认为是混乱的、“软性”领域。然而,近年来人们逐渐认识到,这种“混乱”的表象实际上构成了一项深刻的技术挑战,即如何发现和理解这些极其复杂现象中的秩序。被称为“复杂性”的领域是许多科学的前沿。尽管纯粹的技术任务通常对才华横溢的软件工程师来说似乎最有趣也最具挑战性,但领域驱动设计开辟了一个至少同样具有挑战性的新领域。商业软件不必是拼凑而成的混乱之作。将复杂的领域转化为易于理解的软件设计,对于技术实力雄厚的人来说是一项令人兴奋的挑战。
Weather, ecosystems, and biology used to be considered messy, “soft” fields in contrast to physics or chemistry. Recently, however, people have recognized that the appearance of “messiness” in fact presents a profound technical challenge to discover and understand the order in these very complex phenomena. The field called “complexity” is the vanguard of many sciences. Although purely technological tasks have generally seemed most interesting and challenging to talented software engineers, domain-driven design opens up a new area of challenge that is at least equal. Business software does not have to be a bolted-together mess. Wrestling a complex domain into a comprehensible software design is an exciting challenge for strong technical people.
我们距离普通人能够开发出复杂且实用的软件的时代还很遥远。一大批技能基础薄弱的程序员或许能够开发出某些类型的软件,但却无法开发出那种能在公司危难之际力挽狂澜的软件。我们需要的是工具开发者们集中精力,致力于提升优秀软件开发者的能力和生产力。我们需要的是更高效的……探索领域模型并将其转化为可运行的软件。我期待尝试为此目的而设计的新工具和技术。
We are nowhere near the era of laypeople creating complex software that works. Armies of programmers with rudimentary skills can produce certain kinds of software, but not the kind that saves a company in its eleventh hour. What is needed is for tool builders to put their minds to the task of extending the power and productivity of talented software developers. What is needed are sharper ways of exploring domain models and expressing them in working software. I look forward to experimenting with new tools and technologies devised for this purpose.
虽然改进的工具很有价值,但我们不能被它们分散注意力,而忽略了开发优秀软件的核心事实:这本身就是一个学习和思考的过程。建模需要想象力和自律。能够帮助我们思考或避免分心的工具固然好,但试图将原本需要思考才能完成的事情自动化,则是天真且适得其反的。
But though improved tools will be valuable, we mustn’t get distracted by them and lose sight of the core fact that creating good software is a learning and thinking activity. Modeling requires imagination and self-discipline. Tools that help us think or avoid distraction are good. Efforts to automate what must be the product of thought are naive and counterproductive.
利用我们现有的工具和技术,我们可以构建比当今大多数项目更有价值系统的系统。我们可以编写出既易于使用又令人愉悦的软件,这种软件不会随着发展而束缚我们,反而会创造新的机遇,并持续为所有者创造价值。
With the tools and technology we already have, we can build systems much more valuable than most projects do today. We can write software that is a pleasure to use and a pleasure to work on, software that doesn’t box us in as it grows but creates new opportunities and continues to add value for its owners.
我的第一辆“好车”是大学毕业后不久别人送给我的,那是一辆八年车龄的标致。这辆车有时被称为“法国奔驰”,做工精良,驾驶起来很舒服,而且一直非常可靠。但当我得到它的时候,它已经到了开始出现各种故障、需要更多维修保养的年纪了。
My first “nice car,” which I was given shortly after college, was an eight-year-old Peugeot. Sometimes called the “French Mercedes,” this car was well crafted, was a pleasure to drive, and had been very reliable. But by the time I got it, it was reaching the age when things start to go wrong and more maintenance is required.
标致是一家历史悠久的公司,几十年来一直遵循着自己独特的发展道路。它拥有自己专属的机械术语,设计也独树一帜;甚至连功能部件的分解方式有时也与标准不同。因此,只有标致专家才能维修标致汽车,这对于收入微薄的研究生来说无疑是个难题。
Peugeot is an old company, and it has followed its own evolutionary path over many decades. It has its own mechanical terminology, and its designs are idiosyncratic; even the breakdown of functions into parts is sometimes nonstandard. The result is a car that only Peugeot specialists can work on, a potential problem for someone on a grad student income.
有一次,我把车开到当地一家修车店,想检查一下漏油的问题。修车师傅检查了底盘,告诉我漏油的地方“大概在车尾三分之二的位置,有个小盒子好像跟前后轮的制动力分配有关”。然后他就拒绝修理,建议我去五十英里外的经销商那里。福特和本田车谁都能修,所以这些车虽然机械结构同样复杂,但用起来更方便,成本也更低。
On one typical occasion, I took the car to a local mechanic to investigate a fluid leak. He examined the undercarriage and told me that oil was “leaking from a little box about two-thirds of the way back that seems to have something to do with distributing braking power between front and rear.” He then refused to touch the car and advised me to go to the dealership, fifty miles away. Anyone can work on a Ford or a Honda; that’s why those cars are more convenient and less expensive to own, even though they are equally mechanically complex.
我确实很喜欢那辆车,但我以后再也不会买这种奇奇怪怪的车了。有一天,它被诊断出一个特别昂贵的故障,我对标致车彻底失望了。我把它送到一家接受捐赠车辆的当地慈善机构。然后,我花了和修车费用差不多的价钱,买了一辆破旧的本田思域。
I did love that car, but I will never own a quirky car again. A day came when a particularly expensive problem was diagnosed, and I had had enough of Peugeots. I took it to a local charity that accepted cars as donations. Then I bought a beat-up old Honda Civic for about what the repair would have cost.
领域开发缺乏标准设计元素,因此每个领域模型及其对应的实现都显得古怪且难以理解。此外,每个团队都不得不重新发明轮子(或者齿轮,或者雨刮器)。在面向对象设计的世界里,一切皆为对象、引用或消息——这当然是一种有用的抽象。但这并不能充分限制领域设计选择的范围,也不利于对领域模型进行简洁有效的探讨。
Standard design elements are lacking for domain development, and so every domain model and corresponding implementation is quirky and hard to understand. Moreover, every team has to reinvent the wheel (or the gear, or the windshield wiper). In the world of object-oriented design, everything is an object, a reference, or a message—which, of course, is a useful abstraction. But that does not sufficiently constrain the range of domain design choices and does not support an economical discussion of a domain model.
如果仅仅用“万物皆物”来概括,就好比木匠或建筑师用“万物皆房间”来概括房屋一样。比如,会有带高压插座和水槽的大房间,你可以在那里做饭;楼上会有小房间,你可以在那里睡觉。要描述一栋普通的房子,恐怕要写好几页纸。建造或使用房屋的人都知道,房间遵循一定的模式,这些模式都有专门的名称,比如“厨房”。这种语言使得对房屋设计的讨论更加简洁明了。
To stop with “Everything is an object” would be like a carpenter or an architect summing up houses by saying “Everything is a room.” There would be the big room with high-voltage outlets and a sink, where you might cook. There would be the small room upstairs, where you might sleep. It would take pages to describe an ordinary house. People who build or use houses realize that rooms follow patterns, patterns with special names, such as “kitchen.” This language enables economical discussion of house design.
此外,并非所有功能组合都实用。为什么不把浴室和卧室合二为一呢?那岂不是很方便?但长期的经验已经形成了习惯,我们把“卧室”和“浴室”分开。毕竟,浴室通常比卧室供更多人使用,而且需要最大限度的隐私,即使是与他人共用一个卧室也不例外。浴室的设施也需要专门且昂贵的配置。浴缸和马桶通常位于同一房间,因为它们都需要相同的设施(水和排水),而且都是私人使用。
Moreover, not all combinations of functions turn out to be practical. Why not a room where you bathe and sleep? Wouldn’t that be convenient. But long experience has precipitated into custom, and we separate our “bedrooms” from our “bathrooms.” After all, bathing facilities tend to be shared among more people than bedrooms are, and they require maximum privacy, even from the others who share the same bedroom. And bathrooms have specialized and expensive infrastructure requirements. Bathtubs and toilets typically end up in the same room because both require the same infrastructure (water and drainage) and both are used in private.
另一个对基础设施有特殊要求的房间是准备饭菜的房间,也就是我们常说的“厨房”。与浴室不同,厨房对隐私没有特殊要求。由于成本较高,即使在相对较大的房子里,通常也只有一个厨房。这种单一性也促进了我们共同准备和享用食物的习惯。
Another room that has special infrastructure requirements is that room where you might prepare meals, also known as the “kitchen.” In contrast to the bathroom, a kitchen has no special privacy requirements. Because of its expense, there is typically only one, even in relatively large houses. This singularity also facilitates our communal food preparation and eating customs.
当我说我想要一栋三室两卫、带开放式厨房的房子时,我用一个简短的句子包含了大量的信息,并且避免了很多愚蠢的错误——比如把马桶放在冰箱旁边。
When I say that I want a three-bedroom, two-bath house with an open-plan kitchen, I have packed a huge amount of information into a short sentence, and I’ve avoided a lot of silly mistakes—such as putting a toilet next to the refrigerator.
在设计的各个领域——房屋、汽车、划艇或软件——我们都以过去行之有效的模式为基础,并在此基础上进行即兴发挥。在既定主题框架内,我们有时需要创造全新事物。但通过基于模式构建标准元素,我们可以避免将精力浪费在已有解决方案的问题上,从而专注于满足我们独特的需求。此外,借鉴传统模式还能帮助我们避免设计过于独特而难以沟通。
In every area of design—houses, cars, rowboats, or software—we build on patterns that have been found to work in the past, improvising within established themes. Sometimes we have to invent something completely new. But by basing standard elements on patterns, we avoid wasting our energy on problems with known solutions so that we can focus on our unusual needs. Also, building from conventional patterns helps us avoid a design so idiosyncratic that it is difficult to communicate.
尽管软件领域设计不如其他设计领域成熟——而且无论如何,它可能过于多样化,无法容纳像汽车零件或房间那样具体的模式——但仍然需要超越“一切皆对象”的理念,至少要达到能够区分螺栓和弹簧的程度。
Although software domain design is not as mature as other design fields—and in any case may be too diverse to accommodate patterns as specific as those used for car parts or rooms—there is nonetheless a need to move beyond “Everything is an object” to at least the equivalent of distinguishing bolts from springs.
20世纪70年代,由克里斯托弗·亚历山大(Christopher Alexander)领导的一群建筑师(Alexander et al. 1977)引入了一种用于分享和标准化设计理念的形式。他们的“模式语言”将久经考验的、针对常见问题的设计方案巧妙地融合在一起(比我举的“厨房”例子要精妙得多,后者可能让一些亚历山大的读者感到不适)。其目的是让建造者和使用者能够用这种语言进行交流,并遵循这些模式,从而建造出美观实用、令使用者感到舒适的建筑。
A form for sharing and standardizing design insight was introduced in the 1970s by a group of architects led by Christopher Alexander (Alexander et al. 1977). Their “pattern language” wove together tried-and-true design solutions to common problems (much more subtly than my “kitchen” example, which has probably caused some readers of Alexander to cringe). The intent was that builders and users would communicate in this language, and they would be guided by the patterns to produce beautiful buildings that worked well and felt good to the people who used them.
无论架构师对此有何看法,这种模式语言都对软件设计产生了深远的影响。20世纪90年代,软件模式被应用于诸多领域并取得了一定的成功,尤其是在详细设计(Gamma等人,1995)和技术架构(Buschmann等人,1996)方面。近年来,模式也被用于记录基本的面向对象设计技术(Larman,1998)和企业架构(Fowler,2003;Alur等人,2001)。如今,模式语言已成为组织软件设计理念的主流方法。
Whatever architects might think of the idea, this pattern language has had a big impact on software design. In the 1990s software patterns were applied in many ways with some success, notably in detailed design (Gamma et al. 1995) and technical architectures (Buschmann et al. 1996). More recently, patterns have been used to document basic object-oriented design techniques (Larman 1998) and enterprise architectures (Fowler 2003, Alur et al. 2001). The language of patterns is now a mainstream technique for organizing software design ideas.
这些模式名称旨在成为团队语言中的术语,我在本书中也正是这样使用的。当模式名称出现在讨论中时,会用小型大写字母标出,以便突出显示。
The pattern names are meant to become terms in the language of the team, and I’ve used them that way in this book. When a pattern name appears in a discussion, it is FORMATTED IN SMALL CAPS to call it out.
以下是我在本书中对模式的格式化方式。由于我更注重具体案例的清晰度和可读性,而非僵化的结构,因此在这个基本方案的基础上会有一些变化……
Here is how I’ve formatted patterns in this book. There is some variation around this basic plan, as I have favored case-by-case clarity and readability over rigid structure. . . .
[概念图解。有时采用视觉隐喻或富有启发性的文字。]
[Illustration of concept. Sometimes a visual metaphor or evocative text.]
【背景:简要解释该概念与其他模式的关系。在某些情况下,简要概述该模式。】
[Context. A brief explanation of how the concept relates to other patterns. In some cases, a brief overview of the pattern.
然而,本书中的许多背景讨论都出现在章节引言和其他叙述部分,而不是在模式本身中。
However, much of the context discussion in this book is in the chapter introductions and other narrative segments, rather than within the patterns.
【问题讨论】
[Problem discussion.]
问题概述。
Problem summary.
讨论解决问题的方法迫使人们寻求解决方案。
Discussion of the resolution of problem forces into a solution.
所以:
Therefore:
解决方案总结。
Solution summary.
后果。实施注意事项。示例。
Consequences. Implementation considerations. Examples.
结果背景:简要解释该模式如何导致后续模式。
Resulting context: A brief explanation of how the pattern leads to later patterns.
【关于实现挑战的讨论。在亚历山大的原著中,这部分讨论会被纳入问题解决方案的描述部分,本书中我也经常沿用亚历山大的组织结构。但有些模式需要更详细的实现讨论。为了保持核心模式讨论的简洁性,我将这些较长的实现讨论移到了模式之后。】
[Discussion of implementation challenges. In Alexander’s original format, this discussion would have been folded into the section describing the resolution of the problem, and I have often followed Alexander’s organization in this book. But some patterns demand lengthier discussions of implementation. To keep the core pattern discussion tight, I have moved such long implementation discussions out, after the pattern.
此外,冗长的例子,特别是那些结合了多种模式的例子,往往不符合这些模式。
Also, lengthy examples, particularly those that combine multiple patterns, are often outside the patterns.]
以下是本书中使用的部分术语、模式名称和其他概念的简要定义。
Here are brief definitions of selected terms, pattern names, and other concepts used in the book.
聚合(AGGREGATE)是一组关联对象的集合,在数据变更时被视为一个整体。外部引用仅限于聚合中的一个成员,该成员被指定为根成员。一组一致性规则适用于聚合的边界内。
AGGREGATE A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the AGGREGATE, designated as the root. A set of consistency rules applies within the AGGREGATE’S boundaries.
分析模式:一组概念,代表业务建模中的常见结构。它可能只与一个领域相关,也可能跨越多个领域(Fowler 1997,第 8 页)。
analysis pattern A group of concepts that represents a common construction in business modeling. It may be relevant to only one domain or may span many domains (Fowler 1997, p. 8).
断言是对程序在某一时刻状态是否正确的陈述,与程序如何实现该状态无关。通常,断言用于指定某个操作的结果或设计元素的某个不变量。
ASSERTION A statement of the correct state of a program at some point, independent of how it does it. Typically, an ASSERTION specifies the result of an operation or an invariant of a design element.
限定情境:特定模型的适用范围受到限制。限定情境使团队成员对哪些内容必须保持一致、哪些内容可以独立发展有清晰的共识。
BOUNDED CONTEXT The delimited applicability of a particular model. BOUNDING CONTEXTS gives team members a clear and shared understanding of what has to be consistent and what can develop independently.
client A program element that is calling the element under design, using its capabilities.
cohesion Logical agreement and dependence.
命令(又称修饰符)是对系统进行某种更改的操作(例如,设置变量)。也指有意产生副作用的操作。
command (a.k.a. modifier) An operation that effects some change to the system (for example, setting a variable). An operation that intentionally creates a side effect.
概念轮廓:领域本身所具有的内在一致性,如果这种一致性反映在模型中,可以帮助设计更自然地适应变化。
CONCEPTUAL CONTOUR An underlying consistency of the domain itself, which, if reflected in a model, can help the design accommodate change more naturally.
语境:词语或语句出现的具体环境,决定了其含义。参见“限定语境”。
context The setting in which a word or statement appears that determines its meaning. See BOUNDED CONTEXT.
上下文地图 对项目中涉及的有界上下文及其模型之间的实际关系的表示。
CONTEXT MAP A representation of the BOUNDED CONTEXTS involved in a project and the actual relationships between them and their models.
核心领域:模型中与用户目标密切相关的独特部分,它使应用程序与众不同并使其有价值。
CORE DOMAIN The distinctive part of the model, central to the user’s goals, that differentiates the application and makes it valuable.
声明式设计是一种编程形式,其中对属性的精确描述实际控制着软件。它是一种可执行的规范。
declarative design A form of programming in which a precise description of properties actually controls the software. An executable specification.
深度模型是对领域专家主要关注点及其最相关知识的精辟表达。深度模型摒弃了领域的表面现象和肤浅的解释。
deep model An incisive expression of the primary concerns of the domain experts and their most relevant knowledge. A deep model sloughs off superficial aspects of the domain and naive interpretations.
设计模式是对用于解决特定上下文中的一般设计问题的通信对象和类的描述。(Gamma et al. 1995, p. 3)
design pattern A description of communicating objects and classes that are customized to solve a general design problem in a particular context. (Gamma et al. 1995, p. 3)
蒸馏是指将混合物中的成分分离,提取其精华,使其更有价值和用途的过程。在软件设计中,它指的是对模型中的关键方面进行抽象,或者将大型系统划分成多个部分,以突出核心领域。
distillation A process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. In software design, the abstraction of key aspects in a model, or the partitioning of a larger system to bring the CORE DOMAIN to the fore.
domain A sphere of knowledge, influence, or activity.
领域专家是指软件项目中专长于应用程序领域而非软件开发本身的成员。领域专家并非软件的普通用户,而是对该领域拥有深厚的知识。
domain expert A member of a software project whose field is the domain of the application, rather than software development. Not just any user of the software, the domain expert has deep knowledge of the subject.
领域层是分层架构中负责领域逻辑的设计和实现部分。领域层是领域模型软件表达所在的位置。
domain layer That portion of the design and implementation responsible for domain logic within a LAYERED ARCHITECTURE. The domain layer is where the software expression of the domain model lives.
实体:一个对象,其根本定义并非基于其属性,而是基于连续性和同一性。
ENTITY An object fundamentally defined not by its attributes, but by a thread of continuity and identity.
工厂机制,用于封装复杂的创建逻辑,并为客户端抽象出所创建对象的类型。
FACTORY A mechanism for encapsulating complex creation logic and abstracting the type of a created object for the sake of a client.
function An operation that computes and returns a result without observable side effects.
immutable The property of never changing observable state after creation.
implicit concept A concept that is necessary to understand the meaning of a model or design but is never mentioned.
意图揭示界面:一种设计,其中类、方法和其他元素的名称既传达了原始开发人员创建它们的目的,又传达了它们对客户端开发人员的价值。
INTENTION-REVEALING INTERFACE A design in which the names of classes, methods, and other elements convey both the original developer’s purpose in creating them and their value to a client developer.
不变的断言 关于某些设计元素,这些元素必须始终为真,但某些瞬态情况除外,例如方法执行过程中或未提交的数据库事务执行过程中。
invariant An ASSERTION about some design element that must be true at all times, except during specifically transient situations such as the middle of the execution of a method, or the middle of an uncommitted database transaction.
iteration A process in which a program is repeatedly improved in small steps. Also, one of those steps.
大型结构:一套高层次的概念、规则或两者兼具,它为整个系统建立了一种设计模式。一种允许对系统进行概括性讨论和理解的语言。
large-scale structure A set of high-level concepts, rules, or both that establishes a pattern of design for an entire system. A language that allows the system to be discussed and understood in broad strokes.
分层架构是一种将软件系统关注点分离的技术,其特点之一是隔离领域层。
LAYERED ARCHITECTURE A technique for separating the concerns of a software system, isolating a domain layer, among other things.
生命周期是指对象从创建到删除之间可以经历的一系列状态,通常带有约束条件以确保状态转换时的完整性。也可能包括实体在不同系统和不同有界上下文之间的迁移。
life cycle A sequence of states an object can take on between creation and deletion, typically with constraints to ensure integrity when changing from one state to another. May include migration of an ENTITY between systems and different BOUNDED CONTEXTS.
模型是一个抽象系统,它描述了某个领域的特定方面,并可用于解决与该领域相关的问题。
model A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.
模型驱动设计是一种设计方法,其中软件元素的某些子集与模型元素紧密对应。此外,它也是一种协同开发模型和实现的过程,使二者始终保持一致。
MODEL-DRIVEN DESIGN A design in which some subset of software elements corresponds closely to elements of a model. Also, a process of codeveloping a model and an implementation that stay aligned with each other.
建模范式:一种在某个领域中划分概念的特定风格,结合工具来创建这些概念的软件对应物(例如,面向对象编程和逻辑编程)。
modeling paradigm A particular style of carving out concepts in a domain, combined with tools to create software analogs of those concepts (for example, object-oriented programming and logic programming).
REPOSITORY A mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.
责任履行任务或了解信息的义务(Wirfs-Brock 等人,2003 年,第 3 页)。
responsibility An obligation to perform a task or know information (Wirfs-Brock et al. 2003, p. 3).
SERVICE An operation offered as an interface that stands alone in the model, with no encapsulated state.
副作用:任何由操作引起的、可观察到的状态变化,无论是有意的还是无意的,甚至是刻意的更新。
side effect Any observable change of state resulting from an operation, whether intentional or not, even a deliberate update.
无副作用功能 参见 功能说明。
SIDE-EFFECT-FREE FUNCTION See function.
独立类:无需参考任何其他类(系统原语和基本库除外)即可理解和测试的类。
STANDALONE CLASS A class that can be understood and tested without reference to any others, except system primitives and basic libraries.
无国籍 无状态元素是指设计元素的一种特性,它允许客户端在不考虑元素历史记录的情况下使用其任何操作。无状态元素可以使用全局可访问的信息,甚至可以更改这些全局信息(即,它可能会产生副作用),但它不持有任何影响其行为的私有状态。
stateless The property of a design element that allows a client to use any of its operations without regard to the element’s history. A stateless element may use information that is accessible globally and may even change that global information (that is, it may have side effects) but holds no private state that affects its behavior.
战略设计是指适用于系统大部分部分的建模和设计决策。这类决策会影响整个项目,必须在团队层面做出决定。
strategic design Modeling and design decisions that apply to large parts of the system. Such decisions affect the entire project and have to be decided at team level.
灵活的设计:这种设计将深度模型的强大功能赋予客户端开发人员,使其能够创建清晰、灵活的表达式,并稳健地获得预期结果。同样重要的是,它利用同样的深度模型,使实施人员能够轻松地对设计本身进行调整和修改,以适应新的见解。
supple design A design that puts the power inherent in a deep model into the hands of a client developer to make clear, flexible expressions that give expected results robustly. Equally important, it leverages that same deep model to make the design itself easy for the implementer to mold and reshape to accommodate new insight.
普适语言是一种围绕领域模型构建的语言,所有团队成员都使用它来将团队的所有活动与软件连接起来。
UBIQUITOUS LANGUAGE A language structured around the domain model and used by all team members to connect all the activities of the team with the software.
统一性:模型内部的一致性,使得每个术语都明确无误,并且没有规则相互矛盾。
unification The internal consistency of a model such that each term is unambiguous and no rules contradict.
VALUE OBJECT An object that describes some characteristic or attribute but carries no concept of identity.
WHOLE VALUE An object that models a single, complete concept.
Alexander, C.、M. Silverstein、S. Angel、S. Ishikawa 和 D. Abrams。1975 年。《俄勒冈实验》。牛津大学出版社。
Alexander, C., M. Silverstein, S. Angel, S. Ishikawa, and D. Abrams. 1975. The Oregon Experiment. Oxford University Press.
Alexander, C.、S. Ishikawa 和 M. Silverstein. 1977. 《模式语言:城镇、建筑、施工》。牛津大学出版社。
Alexander, C., S. Ishikawa, and M. Silverstein. 1977. A Pattern Language: Towns, Buildings, Construction. Oxford University Press.
Alur, D.、J. Crupi 和 D. Malks. 2001. Core J2EE Patterns . Sun Microsystems Press.
Alur, D., J. Crupi, and D. Malks. 2001. Core J2EE Patterns. Sun Microsystems Press.
Beck, K. 1997. Smalltalk 最佳实践模式。Prentice Hall PTR。
Beck, K. 1997. Smalltalk Best Practice Patterns. Prentice Hall PTR.
Beck, K. 2000. 《极限编程详解:拥抱变化》。Addison-Wesley出版社。
Beck, K. 2000. Extreme Programming Explained: Embrace Change. Addison-Wesley.
Beck, K. 2003.测试驱动开发:实例详解。Addison-Wesley 出版社。
Beck, K. 2003. Test-Driven Development: By Example. Addison-Wesley.
Buschmann, F.、R. Meunier、H. Rohnert、P. Sommerlad 和 M. Stal. 1996.面向模式的软件架构:模式系统。Wiley。
Buschmann, F., R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal. 1996. Pattern-Oriented Software Architecture: A System of Patterns. Wiley.
Cockburn, A. 1998. 《面向对象项目生存指南:经理人指南》。Addison-Wesley 出版社。
Cockburn, A. 1998. Surviving Object-Oriented Projects: A Manager’s Guide. Addison-Wesley.
Evans, E. 和 M. Fowler. 1997. “规范。” PLoP 97 会议论文集。
Evans, E., and M. Fowler. 1997. “Specifications.” Proceedings of PLoP 97 Conference.
Fayad, M. 和 R. Johnson. 2000.特定领域应用框架. Wiley.
Fayad, M., and R. Johnson. 2000. Domain-Specific Application Frameworks. Wiley.
Fowler, M. 1997.分析模式:可重用对象模型。Addison-Wesley。
Fowler, M. 1997. Analysis Patterns: Reusable Object Models. Addison-Wesley.
Fowler, M. 1999.重构:改进现有代码的设计。Addison-Wesley。
Fowler, M. 1999. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
Fowler, M. 2003.企业应用架构模式。Addison-Wesley。
Fowler, M. 2003. Patterns of Enterprise Application Architecture. Addison-Wesley.
Gamma, E.、R. Helm、R. Johnson 和 J. Vlissides. 1995.设计模式。Addison-Wesley。
Gamma, E., R. Helm, R. Johnson, and J. Vlissides. 1995. Design Patterns. Addison-Wesley.
Kerievsky, J. 2003. “持续学习”,载于《极限编程视角》,Michele Marchesi 等著,Addison-Wesley 出版社。
Kerievsky, J. 2003. “Continuous Learning,” in Extreme Programming Perspectives, Michele Marchesi et al. Addison-Wesley.
Kerievsky, J. 2003. 网站:http://www.industriallogic.com/xp/refactoring。
Kerievsky, J. 2003. Web site: http://www.industriallogic.com/xp/refactoring.
Larman, C. 1998.应用 UML 和模式:面向对象分析与设计导论。Prentice Hall PTR。
Larman, C. 1998. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design. Prentice Hall PTR.
Merriam-Webster. 1993. Merriam-Webster’s Collegiate Dictionary. Tenth edition. Merriam-Webster.
Meyer, B. 1988.面向对象的软件构造. Prentice Hall PTR.
Meyer, B. 1988. Object-oriented Software Construction. Prentice Hall PTR.
Murray-Rust, P.、H. Rzepa 和 C. Leach. 1995.摘要 40。于 1995 年 8 月 21 日在芝加哥举行的第 210 届美国化学学会 (ACS) 会议上以海报形式发表。http ://www.ch.ic.ac.uk/cml/
Murray-Rust, P., H. Rzepa, and C. Leach. 1995. Abstract 40. Presented as a poster at the 210th ACS Meeting in Chicago on August 21, 1995. http://www.ch.ic.ac.uk/cml/
Pinker, S. 1994. 《语言本能:心智如何创造语言》。哈珀柯林斯出版社。
Pinker, S. 1994. The Language Instinct: How the Mind Creates Language. HarperCollins.
Succi, GJ, D. Wells, M. Marchesi 和 L. Williams. 2002.极限编程视角。培生教育出版社。
Succi, G. J., D. Wells, M. Marchesi, and L. Williams. 2002. Extreme Programming Perspectives. Pearson Education.
Warmer, J. 和 A. Kleppe. 1999.对象约束语言:使用 UML 进行精确建模。Addison-Wesley。
Warmer, J., and A. Kleppe. 1999. The Object Constraint Language: Precise Modeling with UML. Addison-Wesley.
Wirfs-Brock, R.、B. Wilkerson 和 L. Wiener. 1990.《面向对象软件设计》。Prentice Hall PTR。
Wirfs-Brock, R., B. Wilkerson, and L. Wiener. 1990. Designing Object-Oriented Software. Prentice Hall PTR.
Wirfs-Brock, R. 和 A. McKean. 2003.对象设计:角色、责任和协作。Addison-Wesley。
Wirfs-Brock, R., and A. McKean. 2003. Object Design: Roles, Responsibilities, and Collaborations. Addison-Wesley.
本书中出现的所有照片均已获得授权使用。
All photographs appearing in this book have been used with permission.
Richard A. Paselk,洪堡州立大学
星盘(第 3 章,第47页)
Richard A. Paselk, Humboldt State University
Astrolabe (Chapter 3, page 47)
© Royalty-Free/Corbis
指纹(第 5 章,第89页),加油站(第 5 章,第104页),汽车工厂(第 6 章,第136页),图书管理员(第 6 章,第147页)
© Royalty-Free/Corbis
Fingerprint (Chapter 5, page 89), Service Station (Chapter 5, page 104), Auto Factory (Chapter 6, page 136), Librarian (Chapter 6, page 147)
Martine Jousset
葡萄(第 6 章,第125页),橄榄树(幼树和老树)(结论,第500-501页)
Martine Jousset
Grapes (Chapter 6, page 125), Olive Trees (young and old)(Conclusion, pages 500–501)
Biophoto Associates/Photo Researchers, Inc.
颤藻的电子显微照片(第 14 章,第335页)
Biophoto Associates/Photo Researchers, Inc.
Electron micrograph of Oscillatoria (Chapter 14, page 335)
Ross J. Venables
赛艇运动员(团体和单人赛艇)(第 14 章,第 341页和371页)
Ross J. Venables
Rowers (group and single) (Chapter 14, pages 341 and 371)
Photodisc Green/Getty Images
跑步者(第 14 章,第356页),儿童(第 14 章,第361页)
Photodisc Green/Getty Images
Runners (Chapter 14, page 356), Child (Chapter 14, page 361)
美国国家海洋和大气管理局《
中国长城》(第14章,第364页)
U.S. National Oceanic and Atmospheric Administration
Great Wall of China (Chapter 14, page 364)
© 2003 NAMES 项目基金会,亚特兰大,佐治亚州。摄影师:
保罗·马戈利斯。www.aidsquilt.org
艾滋病纪念被(第16章,第439页)
© 2003 NAMES Project Foundation, Atlanta, Georgia.
Photographer Paul Margolies. www.aidsquilt.org
AIDS Quilt (Chapter 16, page 439)
A DAPTERS,367
ADAPTERS, 367
例如,130-135、170-171、177-179
examples, 130–135, 170–171, 177–179
本土身份与全球身份,127
local vs. global identity, 127
所有权关系,126
ownership relationships, 126
敏捷设计
Agile design
蒸馏,483
distillation, 483
模块,111
MODULES, 111
减少依赖关系,265,435-437,463
reducing dependencies, 265, 435–437, 463
柔韧设计,243-244,260-264
supple design, 243–244, 260–264
艾滋病纪念被项目,479
AIDS Memorial Quilt Project, 479
分析模式。另见 设计模式。
Analysis patterns. See also design patterns.
定义,293
definition, 293
概述,294
overview, 294
适配器,367
ADAPTERS, 367
relationships with external systems, 384–385
建筑框架,70、74、156-157、271-272、495-496
Architectural frameworks, 70, 74, 156–157, 271–272, 495–496
星盘,47
Astrolabe, 47
Awkwardness, concept analysis, 210–216
Bidirectional associations, 102–103
Blind men and the elephant, 378–381
有界上下文。另见 上下文地图。
BOUNDED CONTEXT. See also CONTEXT MAP.
代码重用,344
code reuse, 344
CONTINUOUS INTEGRATION, 341–343
定义,382
defining, 382
large-scale structure, 485–488
测试边界,351
testing boundaries, 351
translation layers, 374. See also ANTICORRUPTION LAYER; PUBLISHED LANGUAGE.
与 模块相比,335
vs. MODULES, 335
头脑风暴,7-13,207-216,219
Brainstorming, 7–13, 207–216, 219
突破,193-200,202-203
Breakthroughs, 193–200, 202–203
用户界面层中的业务逻辑,77
Business logic, in user interface layer, 77
回调函数,73
Callbacks, 73
货物运输示例。参见 货物运输示例。
Cargo shipping examples. See examples, cargo shipping.
更改设计。参见 重构。
Changing the design. See refactoring.
Chemical warehouse packer example, 235–241
化学示例,377
Chemistry example, 377
克里斯,约翰,5
Cleese, John, 5
CLOSURE OF OPERATIONS, 268–270
代码即文档,40
Code as documentation, 40
代码重用
Code reuse
有界上下文,344
BOUNDED CONTEXT, 344
凝聚力,模块,109-110,113
Cohesion, MODULES, 109–110, 113
内聚机制
COHESIVE MECHANISMS
and declarative style, 426–427
与 通用子域相比,425
vs. GENERIC SUBDOMAINS, 425
Common language. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
沟通,言语。参见 “普遍语言”。
Communication, speech. See UBIQUITOUS LANGUAGE.
书面沟通。参见 文档;UML(统一建模语言);通用语言。
Communication, written. See documents; UML (Unified Modeling Language); UBIQUITOUS LANGUAGE.
复杂性,简化。参见 提炼;大规模结构;分层架构; 灵活设计。
Complexity, reducing. See distillation; large-scale structure; LAYERED ARCHITECTURE; supple design.
Composite SPECIFICATION, 273–282
Concept analysis. See also analysis patterns; examples, concept analysis.
language of the domain experts, 206–207
processes as domain objects, 222–223
researching existing resources, 217–219
规格,223
SPECIFICATION, 223
反复试验,219
trial and error, 219
Conceptual layers, See LAYERED ARCHITECTURE; RESPONSIBILITY LAYERS
Configuring SPECIFICATION, 226–227
顺从者,361-363,384-385
Constructors, 141–142, 174–175. See also FACTORIES.
上下文映射。另见 有界上下文。
CONTEXT MAP. See also BOUNDED CONTEXT.
organizing and documenting, 351–352
vs. large-scale structure, 446, 485–488
背景地图,选择策略
CONTEXT MAP, choosing a strategy
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS, 356–360
定义有界上下文,382
defining BOUNDED CONTEXT, 382
部署,387
deployment, 387
merging OPEN HOST SERVICE and PUBLISHED LANGUAGE, 394–396
合并SEPARATE WAYS和SHARED KERNEL,389 – 391
merging SEPARATE WAYS and SHARED KERNEL, 389–391
merging SHARED KERNEL and CONTINUOUS INTEGRATION, 391–393
包装,387
packaging, 387
phasing out legacy systems, 393–394
for a project in progress, 388–389
specialized terminologies, 386–387
团队背景,382
team context, 382
权衡取舍,387
trade-offs, 387
变换,389
transformations, 389
transforming boundaries, 382–383
上下文原则,328-329 。另见 有界上下文;上下文地图。
Context principle, 328–329. See also BOUNDED CONTEXT; CONTEXT MAP.
连续积分,341-343,391-393 。另见积分。
CONTINUOUS INTEGRATION, 341–343, 391–393. See also integration.
Contradictions, concept analysis, 216–217
核心领域
CORE DOMAIN
DOMAIN VISION STATEMENT, 415–416
flagging key elements, 419–420
机制,425
MECHANISMS, 425
Costs of architecture dictated MODULES, 114–115
以客户为中心的团队,492
Customer-focused teams, 492
数据库调优示例,102
Database tuning, example, 102
声明式设计风格,273-282,426-427
Declarative style of design, 273–282, 426–427
与客户端解耦,156
Decoupling from the client, 156
深度模型
Deep models
Deployment, 387. See also MODULES.
设计变更。参见 重构。
Design changes. See refactoring.
设计模式。另见 分析模式。
Design patterns. See also analysis patterns.
蝇量级,320
FLYWEIGHT, 320
与域模式相比, 309
vs. domain patterns, 309
开发团队。参见 团队。
Development teams. See teams.
图表。参见 文档;UML(统一建模语言)。
Diagrams. See documents; UML (Unified Modeling Language).
蒸馏。另见 示例,蒸馏。
Distillation. See also examples, distillation.
DOMAIN VISION STATEMENT, 415–416
INTENTION-REVEALING INTERFACES, 422–427
大型结构,483,488-489
large-scale structure, 483, 488–489
重构目标,437
refactoring targets, 437
在设计中的作用,329
role in design, 329
separating CORE concepts, 428–434
蒸馏,内聚机制
Distillation, COHESIVE MECHANISMS
and declarative style, 426–427
与 通用子域相比,425
vs. GENERIC SUBDOMAINS, 425
蒸馏,核心领域
Distillation, CORE DOMAIN
DOMAIN VISION STATEMENT, 415–416
flagging key elements, 419–420
机制,425
MECHANISMS, 425
蒸馏,通用子域
Distillation, GENERIC SUBDOMAINS
改编已发表的设计,408
adapting a published design, 408
现成解决方案,407
off-the-shelf solutions, 407
概述,406
overview, 406
与 内聚机制相比,425
vs. COHESIVE MECHANISMS, 425
蒸馏文件,418-419,420-421
Distillation document, 418–419, 420–421
代码即文档,40
code as documentation, 40
蒸馏文件,418-419,420-421
distillation document, 418–419, 420–421
DOMAIN VISION STATEMENT, 415–416
gathering requirements from. See concept analysis; knowledge crunching.
language of, 206–207. See also UBIQUITOUS LANGUAGE.
领域对象,生命周期,123 – 124。另请参阅 聚合;工厂;存储库。
Domain objects, life cycle, 123–124. See also AGGREGATES; FACTORIES; REPOSITORIES.
领域模式与设计模式,309
Domain patterns vs. design pattern, 309
DOMAIN VISION STATEMENT, 415–416
Domain-specific language, 272–273
Elephant and the blind men, 378–381
封装。另请参阅 工厂。
Encapsulation. See also FACTORIES.
意图揭示界面,246
INTENTION-REVEALING INTERFACES, 246
存储库,154
REPOSITORIES, 154
ENTITIES. See also associations; SERVICES; VALUE OBJECTS.
聚类。参见 AGGREGATES。
clustering. See AGGREGATES.
ID唯一性,96
ID uniqueness, 96
referencing with VALUE OBJECTS, 98–99
与Java 实体 Bean相比, 91
vs. Java entity beans, 91
示例
Examples
chemical warehouse packer, 235–241
化学,已发表语言,377
chemistry, PUBLISHED LANGUAGE, 377
CLOSURE OF OPERATIONS, 269–270
composite SPECIFICATION, 278–282
extracting hidden concepts, 17–20
integration with other systems, 372–373
INTENTION-REVEALING INTERFACES, 423–424
introducing new features, 181–185
package coding in Java, 111–112
油漆混合应用,247-249,252-254,256-259
paint-mixing application, 247–249, 252–254, 256–259
PLUGGABLE COMPONENT FRAMEWORK, 475–479
已出版语言,377
PUBLISHED LANGUAGE, 377
purchase order integrity, 130–135
RESPONSIBILITY LAYERS, 452–460
selecting from Collections, 269–270
SEMATECH CIM framework, 476–479
无副作用功能,252-254,285-286
SIDE-EFFECT-FREE FUNCTIONS, 252–254, 285–286
数据库调优,102
tuning a database, 102
值对象,102
VALUE OBJECTS, 102
骨料,170-171,177-179
预订
booking
extracting hidden concepts, 17–20
概念分析,222
concept analysis, 222
extracting hidden concepts, 17–20
identifying missing concepts, 207–210
large-scale structure, 452–460
multiple development teams, 358–360
RESPONSIBILITY LAYERS, 452–460
shipping operations and routes, 41–43
extracting hidden concepts, 17–20
identifying missing concepts, 207–210
researching existing resources, 217–219
resolving awkwardness, 211–215
内聚机制,423-424,425-427
COHESIVE MECHANISMS, 423–424, 425–427
organization chart, 423–424, 425–427
例如,集成
Examples, integration
Examples, large-scale structure
PLUGGABLE COMPONENT FRAMEWORK, 475–479
RESPONSIBILITY LAYERS, 452–460
Examples, LAYERED ARCHITECTURE
partitioning applications, 71–72
RESPONSIBILITY LAYERS, 452–460
概念分析,211-215,217-219
concept analysis, 211–215, 217–219
利息计算器,211-215,217-219,295-306
interest calculator, 211–215, 217–219, 295–306
重构,194-200,284-292
Explicit constraints, concept analysis, 220–222
External systems, 383–385. See also integration.
Extracting hidden concepts, 17–20. See also implicit concepts.
设施,194
Facilities, 194
configuring SPECIFICATION, 226–227
界面设计,143
designing the interface, 143
ENTITY vs. VALUE OBJECT, 144–145
不变逻辑,143
invariant logic, 143
要求,139
requirements, 139
电影剪辑轶事,5
Film editing anecdote, 5
灵活性。参见 柔韧设计。
Flexibility. See supple design.
F轻量级图案,320
FLYWEIGHT pattern, 320
功能,无副作用,250-254,285-286
Functions, SIDE-EFFECT-FREE, 250–254, 285–286
通用亚域
GENERIC SUBDOMAINS
改编已发表的设计,408
adapting a published design, 408
现成解决方案,407
off-the-shelf solutions, 407
概述,406
overview, 406
与 内聚机制相比,425
vs. COHESIVE MECHANISMS, 425
粒度,108
Granularity, 108
Hidden concepts, extracting, 17–20
圣杯轶事,5
Holy Grail anecdote, 5
身份
Identity
本地化与全局化,127
local vs. global, 127
Immutability of VALUE OBJECTS, 100–101
基础设施层,70
Infrastructure layer, 70
Infrastructure-driven packaging, 112–116
In-house solution, GENERIC SUB DOMAINS, 409–410
Insurance project example, 372–373
连续积分,341-343,391-393
CONTINUOUS INTEGRATION, 341–343, 391–393
cost/benefit analysis, 371–373
elephant and the blind men, 378–381
开放主机服务,374
OPEN HOST SERVICE, 374
translation layers, 374. See also PUBLISHED LANGUAGE.
完整性。参见 模型完整性。
Integrity. See model integrity.
意图揭示界面,246-249,422-427
INTENTION-REVEALING INTERFACES, 246–249, 422–427
利息计算器示例,211-215、217-219、295-306
Interest calculator examples, 211–215, 217–219, 295–306
Internet Explorer bookmark anecdote, 57–59
不变逻辑,128-129,143
Inventory management example, 504–505
投资银行案例,194-200,211-215,501
Investment banking example, 194–200, 211–215, 501
Isolated domain layer, 106–107
Isolating the domain. See ANTICORRUPTION LAYER; distillation; LAYERED ARCHITECTURE.
迭代设计过程,14,188,445
Iterative design process, 14, 188, 445
Jargon. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
Java实体Bean与 实体(ENTITIES)的区别,91
Java entity beans vs. ENTITIES, 91
Knowledge crunching, example, 7–12
Language of the domain experts, 206–207
大规模结构。另见 提炼;示例,大规模结构;分层架构;战略设计。
Large-scale structure. See also distillation; examples, large-scale structure; LAYERED ARCHITECTURE; strategic design.
上下文地图,446
CONTEXT MAP, 446
定义,442
definition, 442
development constraints, 445–446
极简主义,481
minimalism, 481
PLUGGABLE COMPONENT FRAMEWORK, 475–479
重构,481
refactoring, 481
在设计中的作用,329
role in design, 329
团队沟通,482
team communication, 482
大型结构,责任层级
Large-scale structure, RESPONSIBILITY LAYERS
有用的特性,461
useful characteristics, 461
LAYERED ARCHITECTURE. See also distillation; examples, LAYERED ARCHITECTURE; large-scale structure.
回调,73
callbacks, 73
概念层次,70
conceptual layers, 70
图68
diagram, 68
基础设施层,70
infrastructure layer, 70
isolated domain layer, 106–107
MVC(模型-视图-控制器),73
MVC (MODEL-VIEW-CONTROLLER), 73
观察者,73
OBSERVERS, 73
复杂程序的划分,70
partitioning complex programs, 70
separating user interface, application, and domain, 76–79
智能用户界面,73
SMART UI, 73
交易脚本,79
TRANSACTION SCRIPT, 79
user interface layer, 70, 76–79
值为69
value of, 69
分层架构,反腐败层
LAYERED ARCHITECTURE, ANTICORRUPTION LAYER
适配器,367
ADAPTERS, 367
relationships with external systems, 384–385
分层架构,责任层
LAYERED ARCHITECTURE, RESPONSIBILITY LAYERS
有用的特性,461
useful characteristics, 461
Legacy systems, phasing out, 393–394
领域对象的生命周期,123 – 124。另请参阅 聚合;工厂;存储库。
Life cycle of domain objects, 123–124. See also AGGREGATES; FACTORIES; REPOSITORIES.
贷款管理示例。参见 示例,贷款管理。
Loan management examples. See examples, loan management.
本土身份与全球身份,127
Local vs. global identity, 127
合并
Merging
OPEN HOST SERVICE and PUBLISHED LANGUAGE, 394–396
SEPARATE WAYS to SHARED KERNEL, 389–391
SHARED KERNEL to CONTINUOUS INTEGRATION, 391–393
METADATA映射层,149
METADATA MAPPING LAYERS, 149
身份误认轶事,89
Mistaken identity anecdote, 89
Model integrity. See also BOUNDED CONTEXT; CONTEXT MAP; multiple models.
establishing boundaries, 333–334
多款模型,333
multiple models, 333
recognizing relationships, 333–334
unification, 332. See also CONTINUOUS INTEGRATION.
模型层。参见 领域层。
Model layer. See domain layer.
基于模型的语言。参见 普适语言。
Model-based language. See UBIQUITOUS LANGUAGE.
correspondence to design, 50–51
概述,49
overview, 49
模型的相关性,49
relevance of model, 49
造型
Modeling
integrating with programming, 60–62
模型
Models
与实现绑定。参见 模型驱动设计。
binding to implementation. See MODEL-DRIVEN DESIGN.
模型-视图-控制器(MVC),73
MODEL-VIEW-CONTROLLER (MVC), 73
敏捷,111
agile, 111
确定含义,110
determining meaning of, 110
例如,111-112,179-181
infrastructure-driven packaging, 112–116
命名,110
naming, 110
概述,109
overview, 109
打包领域对象,115
packaging domain objects, 115
与 有限上下文相比,335
vs. BOUNDED CONTEXT, 335
蒙提·派森轶事,5
Monty Python anecdote, 5
多种型号,333、335-340
MVC(模型-视图-控制器),73
MVC (MODEL-VIEW-CONTROLLER), 73
命名
Naming
有界语境,345
BOUNDED CONTEXTS, 345
柔性设计规范,247
conventions for supple design, 247
意图揭示界面,247
INTENTION-REVEALING INTERFACES, 247
模块,110
MODULES, 110
服务,105
SERVICES, 105
对象引用。请参阅 REPOSITORIES。
Object references. See REPOSITORIES.
Objects. See also ENTITIES; VALUE OBJECTS.
creating, 234–235. See also constructors; FACTORIES.
designing for relational databases, 159–161
made up of objects. See AGGREGATES; COMPOSITE.
O BSERVERS,73
OBSERVERS, 73
现成解决方案,407
Off-the-shelf solutions, 407
OPEN HOST SERVICE, converting to PUBLISHED LANGUAGE, 394–396
Overbooking examples, 18–19, 222
Packaging. See deployment; MODULES.
油漆混合应用实例,第247-249页,第252-254页,第256-259页
Paint-mixing application, examples, 247–249, 252–254, 256–259
分区
Partitioning
complex programs. See large-scale structure; LAYERED ARCHITECTURE.
将服务分层,107
SERVICES into layers, 107
Patterns, 507–510. See also analysis patterns; design patterns; large-scale structure.
PCB design anecdote, 7–13, 501
Performance tuning, example, 185–186
PLUGGABLE COMPONENT FRAMEWORK, 475–479
POLICY pattern. See STRATEGY pattern.
表示层。参见 用户界面层。
Presentation layer. See user interface layer.
Procedural languages, and MODEL-DRIVEN DESIGN, 51–54
Processes as domain objects, 222–223
elephant and the blind men, 378–381
例如,377
example, 377
与OPEN HOST SERVICE合并,394 – 396
merging with OPEN HOST SERVICE, 394–396
拼布项目,479
Quilt project, 479
重建,145-146,148
定义,188
definition, 188
为开发者设计,324
designing for developers, 324
蒸馏,437
distillation, 437
例如,177-179、181-185、194-200、247-249
examples, 177–179, 181–185, 194–200, 247–249
大型结构,481
large-scale structure, 481
柔韧设计,191
supple design, 191
重构目标,437
Refactoring targets, 437
引用对象。参见 实体。
Reference objects. See ENTITIES.
优势,152
advantages, 152
architectural frameworks, 156–157
与客户端解耦,156
decoupling from the client, 156
designing objects for relational databases, 159–161
封装,154
encapsulation, 154
元数据映射层,149
METADATA MAPPING LAYERS, 149
对已存在领域对象的引用,149
references to preexisting domain objects, 149
交易控制,156
transaction control, 156
瞬态物体,149
transient objects, 149
Requirements gathering. See concept analysis; knowledge crunching; UBIQUITOUS LANGUAGE.
有用的特性,461
useful characteristics, 461
代码重用
Reusing code
有界上下文,344
BOUNDED CONTEXT, 344
选择对象,229-234,269-270
Selecting objects, 229–234, 269–270
分道扬镳,384-385,389-391
SEPARATE WAYS, 384–385, 389–391
SERVICES. See also ENTITIES; VALUE OBJECTS.
访问权限,108
access to, 108
粒度,108
granularity, 108
and the isolated domain layer, 106–107
命名,105
naming, 105
分层划分,107
partitioning into layers, 107
例如,359
example, 359
merging with CONTINUOUS INTEGRATION, 391–393
merging with SEPARATE WAYS, 389–391
Sharing VALUE OBJECTS, 100–101
运输示例。参见 示例,货物运输。
Shipping examples. See examples, cargo shipping.
Side effects, 250. See also ASSERTIONS.
无副作用功能,250-254,285-286
SIDE-EFFECT-FREE FUNCTIONS, 250–254, 285–286
Simplifying your design. See distillation; large-scale structure; LAYERED ARCHITECTURE.
S MART UI,73
SMART UI, 73
SPECIFICATION. See also analysis patterns; design patterns.
申请,227
applying, 227
业务规则,225
business rules, 225
组合。参见 复合材料规格。
combining. See composite SPECIFICATION.
例如,29、235-241、279-282
实施,227
implementing, 227
目的,227
purpose, 227
验证对象,227、228-229
validating objects, 227, 228–229
口语,通用语言。参见 “普遍语言”。
Speech, common language. See UBIQUITOUS LANGUAGE.
Speech, modeling through, 30–32
战略设计。另见 大规模结构。
Strategic design. See also large-scale structure.
评估形势,490
assessing the situation, 490
以客户为中心的架构团队,492
customer-focused architecture teams, 492
开发人员,角色,494
developers, role of, 494
essential requirements, 492–495
进化,493
evolution, 493
演化秩序,491
EVOLVING ORDER, 491
反馈过程,493
feedback process, 493
多个开发团队,491
multiple development teams, 491
物体,角色,494
objects, role of, 494
团队沟通,492
team communication, 492
团队构成,494
team makeup, 494
策略模式,19,311-314
CLOSURE OF OPERATIONS, 268–270
composite SPECIFICATION, 273–282
declarative style of design, 273–282
domain-specific language, 272–273
INTENTION-REVEALING INTERFACES, 246–249
large-scale structure, 480–483
命名规则,247
naming conventions, 247
无副作用功能,250-254,285-286
SIDE-EFFECT-FREE FUNCTIONS, 250–254, 285–286
团队背景,382
Team context, 382
选择策略,382
choosing a strategy, 382
通信,大型结构,482
communication, large-scale structure, 482
以客户为中心,492
customer-focused, 492
定义有界上下文,382
defining BOUNDED CONTEXT, 382
developer community, maturity of, 117–119
团队和战略设计
Teams, and strategic design
沟通,492
communication, 492
以客户为中心,492
customer-focused, 492
开发人员,角色,494
developers, role of, 494
组成,494
makeup of, 494
多支队伍,491
multiple teams, 491
团队,多个
Teams, multiple
共享内核,354-355,359
战略设计,491
strategic design, 491
Terminology. See BOUNDED CONTEXT; PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
测试边界,351
Testing boundaries, 351
交易控制,156
Transaction control, 156
交易记录,79
TRANSACTION SCRIPT, 79
变换,389
Transformations, 389
Transforming boundaries, 382–383
瞬态物体,149
Transient objects, 149
翻译层,374
Translation layers, 374
数据库调优示例,102
Tuning a database, example, 102
U普遍使用的语言。另见 已出版的语言。
UBIQUITOUS LANGUAGE. See also PUBLISHED LANGUAGE.
designing objects for relational databases, 160–161
domain-specific language, 272–273
language of the domain experts, 206–207
specialized terminologies, 386–387
需求分析,25
requirements analysis, 25
UML (Unified Modeling Language), 35–37
Unification, 332. See also CONTINUOUS INTEGRATION.
Unified Modeling Language (UML), 35–37
更新设计。参见 重构。
Updating the design. See refactoring.
业务逻辑,77
business logic, 77
定义,70
definition, 70
separating from application and domain, 76–79
验证对象,227、228-229
Validating objects, 227, 228–229
VALUE OBJECTS. See also ENTITIES; SERVICES.
bidirectional associations, 102–103
变革管理入门
change management, 101
聚类。参见 AGGREGATES。
clustering. See AGGREGATES.
作为参数传递,99
passing as parameters, 99
数据库调优示例,102
tuning a database, example, 102
愿景声明。请参阅 领域愿景声明。
Vision statement. See DOMAIN VISION STATEMENT.
Vocabulary. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
瀑布式设计方法,14
Waterfall design method, 14
1. Brian Marick 向我提到了这个例子。
1. Brian Marick mentioned this example to me.
1.模型实体与 Java 的“实体 Bean”并不相同。实体 Bean 的设计初衷是作为实现实体的框架,但实际情况并非如此。大多数实体都是以普通对象的形式实现的。无论实现方式如何,实体都是领域模型中的一个基本概念。
1. A model ENTITY is not the same thing as a Java “entity bean.” Entity beans were meant as a framework for implementing ENTITIES, more or less, but it hasn’t worked out that way. Most ENTITIES are implemented as ordinary objects. Regardless of how they are implemented, ENTITIES are a fundamental distinction in a domain model.
2. Ward Cunningham 的“整体价值”模式。
2. The WHOLE VALUE pattern, by Ward Cunningham.
1.大卫·西格尔在 20 世纪 90 年代设计并运用了这套系统,但他并未发表。
1. David Siegel devised and used this system on projects in the 1990s but has not published it.
1. Gamma 等人 ( 1995 ) 曾简要提及将模式作为重构的目标。Joshua Kerievsky 将针对模式的重构发展成了一种更成熟、更有用的形式 ( Kerievsky 2003 )。
1. Patterns as targets for refactoring were briefly mentioned in Gamma et al. (1995). Joshua Kerievsky has developed refactoring to patterns into a more mature and useful form (Kerievsky 2003).
1. Ward Cunningham 的“整体价值”模式。
1. The WHOLE VALUE pattern, by Ward Cunningham.
1.里根翻译了一句古老的俄罗斯谚语,这句谚语概括了双方问题的核心——这是弥合不同背景的另一个比喻。
1. Reagan translated an old Russian saying that summed up the heart of the matter for both sides—another metaphor for bridging contexts.
1.当我在一次研讨会讲座中听到 Ward Cunningham 使用防火墙的例子时,我终于理解了系统隐喻的含义。
1. SYSTEM METAPHOR finally made sense to me when I heard Ward Cunningham use this firewall example in a workshop lecture.
2. POSA是Pattern-Oriented Software Architecture(面向模式的软件架构)的缩写,由 Buschmann 等人于 1996 年提出。
2. POSA is short for Pattern-Oriented Software Architecture, by Buschmann et al. 1996.